1//===-- sancov.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// This file is a command-line tool for reading and analyzing sanitizer
9// coverage.
10//===----------------------------------------------------------------------===//
11#include "llvm/ADT/STLExtras.h"
12#include "llvm/ADT/StringExtras.h"
13#include "llvm/ADT/Twine.h"
14#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
15#include "llvm/DebugInfo/Symbolize/Symbolize.h"
16#include "llvm/MC/MCAsmInfo.h"
17#include "llvm/MC/MCContext.h"
18#include "llvm/MC/MCDisassembler/MCDisassembler.h"
19#include "llvm/MC/MCInst.h"
20#include "llvm/MC/MCInstrAnalysis.h"
21#include "llvm/MC/MCInstrInfo.h"
22#include "llvm/MC/MCObjectFileInfo.h"
23#include "llvm/MC/MCRegisterInfo.h"
24#include "llvm/MC/MCSubtargetInfo.h"
25#include "llvm/MC/MCTargetOptions.h"
26#include "llvm/MC/TargetRegistry.h"
27#include "llvm/Object/Archive.h"
28#include "llvm/Object/Binary.h"
29#include "llvm/Object/COFF.h"
30#include "llvm/Object/MachO.h"
31#include "llvm/Object/ObjectFile.h"
32#include "llvm/Object/XCOFFObjectFile.h"
33#include "llvm/Option/ArgList.h"
34#include "llvm/Option/Option.h"
35#include "llvm/Support/Casting.h"
36#include "llvm/Support/CommandLine.h"
37#include "llvm/Support/Driver.h"
38#include "llvm/Support/Errc.h"
39#include "llvm/Support/ErrorOr.h"
40#include "llvm/Support/FileSystem.h"
41#include "llvm/Support/JSON.h"
42#include "llvm/Support/MD5.h"
43#include "llvm/Support/MemoryBuffer.h"
44#include "llvm/Support/Path.h"
45#include "llvm/Support/Regex.h"
46#include "llvm/Support/SHA1.h"
47#include "llvm/Support/SourceMgr.h"
48#include "llvm/Support/SpecialCaseList.h"
49#include "llvm/Support/TargetSelect.h"
50#include "llvm/Support/VirtualFileSystem.h"
51#include "llvm/Support/YAMLParser.h"
52#include "llvm/Support/raw_ostream.h"
53
54#include <set>
55#include <vector>
56
57using namespace llvm;
58
59namespace {
60
61// Command-line option boilerplate.
62namespace {
63using namespace llvm::opt;
64enum ID {
65 OPT_INVALID = 0, // This is not an option ID.
66#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
67#include "Opts.inc"
68#undef OPTION
69};
70
71#define OPTTABLE_CODE
72#include "Opts.inc"
73
74class SancovOptTable : public opt::OptTable {
75public:
76 SancovOptTable() : OptTable(optionTables()) {}
77};
78} // namespace
79
80// --------- COMMAND LINE FLAGS ---------
81
82enum ActionType {
83 CoveredFunctionsAction,
84 DiffAction,
85 HtmlReportAction,
86 MergeAction,
87 NotCoveredFunctionsAction,
88 PrintAction,
89 PrintCovPointsAction,
90 StatsAction,
91 SymbolizeAction,
92 UnionAction
93};
94
95static ActionType Action;
96static std::vector<std::string> ClInputFiles;
97static bool ClDemangle;
98static bool ClSkipDeadFiles;
99static bool ClUseDefaultIgnorelist;
100static std::string ClStripPathPrefix;
101static std::string ClIgnorelist;
102static std::string ClOutputFile;
103
104static const char *const DefaultIgnorelistStr = "fun:__sanitizer_.*\n"
105 "src:/usr/include/.*\n"
106 "src:.*/libc\\+\\+/.*\n";
107
108// --------- FORMAT SPECIFICATION ---------
109
110struct FileHeader {
111 uint32_t Bitness;
112 uint32_t Magic;
113};
114
115static const uint32_t BinCoverageMagic = 0xC0BFFFFF;
116static const uint32_t Bitness32 = 0xFFFFFF32;
117static const uint32_t Bitness64 = 0xFFFFFF64;
118
119static const Regex SancovFileRegex("(.*)\\.[0-9]+\\.sancov");
120static const Regex SymcovFileRegex(".*\\.symcov");
121
122// --------- MAIN DATASTRUCTURES ----------
123
124// Contents of .sancov file: list of coverage point addresses that were
125// executed.
126struct RawCoverage {
127 explicit RawCoverage(std::unique_ptr<std::set<uint64_t>> Addrs,
128 FileHeader Header)
129 : Addrs(std::move(Addrs)), Header(Header) {}
130
131 // Read binary .sancov file.
132 static ErrorOr<std::unique_ptr<RawCoverage>>
133 read(const std::string &FileName);
134
135 // Write binary .sancov file.
136 static void write(const std::string &FileName, const RawCoverage &Coverage);
137
138 std::unique_ptr<std::set<uint64_t>> Addrs;
139 FileHeader Header;
140};
141
142// Coverage point has an opaque Id and corresponds to multiple source locations.
143struct CoveragePoint {
144 explicit CoveragePoint(const std::string &Id) : Id(Id) {}
145
146 std::string Id;
147 SmallVector<DILineInfo, 1> Locs;
148};
149
150// Symcov file content: set of covered Ids plus information about all available
151// coverage points.
152struct SymbolizedCoverage {
153 // Read json .symcov file.
154 static std::unique_ptr<SymbolizedCoverage> read(const std::string &InputFile);
155
156 std::set<std::string> CoveredIds;
157 std::string BinaryHash;
158 std::vector<CoveragePoint> Points;
159};
160
161struct CoverageStats {
162 size_t AllPoints;
163 size_t CovPoints;
164 size_t AllFns;
165 size_t CovFns;
166};
167
168// --------- ERROR HANDLING ---------
169
170static void fail(const llvm::Twine &E) {
171 errs() << "ERROR: " << E << "\n";
172 exit(status: 1);
173}
174
175static void failIf(bool B, const llvm::Twine &E) {
176 if (B)
177 fail(E);
178}
179
180static void failIfError(std::error_code Error) {
181 if (!Error)
182 return;
183 errs() << "ERROR: " << Error.message() << "(" << Error.value() << ")\n";
184 exit(status: 1);
185}
186
187template <typename T> static void failIfError(const ErrorOr<T> &E) {
188 failIfError(E.getError());
189}
190
191static void failIfError(Error Err) {
192 if (Err) {
193 logAllUnhandledErrors(E: std::move(Err), OS&: errs(), ErrorBanner: "ERROR: ");
194 exit(status: 1);
195 }
196}
197
198template <typename T> static void failIfError(Expected<T> &E) {
199 failIfError(E.takeError());
200}
201
202static void failIfNotEmpty(const llvm::Twine &E) {
203 if (E.str().empty())
204 return;
205 fail(E);
206}
207
208template <typename T>
209static void failIfEmpty(const std::unique_ptr<T> &Ptr,
210 const std::string &Message) {
211 if (Ptr.get())
212 return;
213 fail(E: Message);
214}
215
216// ----------- Coverage I/O ----------
217template <typename T>
218static void readInts(const char *Start, const char *End,
219 std::set<uint64_t> *Ints) {
220 const T *S = reinterpret_cast<const T *>(Start);
221 const T *E = reinterpret_cast<const T *>(End);
222 std::copy(S, E, std::inserter(x&: *Ints, i: Ints->end()));
223}
224
225ErrorOr<std::unique_ptr<RawCoverage>>
226RawCoverage::read(const std::string &FileName) {
227 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
228 MemoryBuffer::getFile(Filename: FileName);
229 if (!BufOrErr)
230 return BufOrErr.getError();
231 std::unique_ptr<MemoryBuffer> Buf = std::move(BufOrErr.get());
232 if (Buf->getBufferSize() < 8) {
233 errs() << "File too small (<8): " << Buf->getBufferSize() << '\n';
234 return make_error_code(E: errc::illegal_byte_sequence);
235 }
236 const FileHeader *Header =
237 reinterpret_cast<const FileHeader *>(Buf->getBufferStart());
238
239 if (Header->Magic != BinCoverageMagic) {
240 errs() << "Wrong magic: " << Header->Magic << '\n';
241 return make_error_code(E: errc::illegal_byte_sequence);
242 }
243
244 auto Addrs = std::make_unique<std::set<uint64_t>>();
245
246 switch (Header->Bitness) {
247 case Bitness64:
248 readInts<uint64_t>(Start: Buf->getBufferStart() + 8, End: Buf->getBufferEnd(),
249 Ints: Addrs.get());
250 break;
251 case Bitness32:
252 readInts<uint32_t>(Start: Buf->getBufferStart() + 8, End: Buf->getBufferEnd(),
253 Ints: Addrs.get());
254 break;
255 default:
256 errs() << "Unsupported bitness: " << Header->Bitness << '\n';
257 return make_error_code(E: errc::illegal_byte_sequence);
258 }
259
260 // Ignore slots that are zero, so a runtime implementation is not required
261 // to compactify the data.
262 Addrs->erase(x: 0);
263
264 return std::make_unique<RawCoverage>(args: std::move(Addrs), args: *Header);
265}
266
267// Print coverage addresses.
268raw_ostream &operator<<(raw_ostream &OS, const RawCoverage &CoverageData) {
269 for (auto Addr : *CoverageData.Addrs) {
270 OS << "0x";
271 OS.write_hex(N: Addr);
272 OS << "\n";
273 }
274 return OS;
275}
276
277// Write coverage addresses in binary format.
278void RawCoverage::write(const std::string &FileName,
279 const RawCoverage &Coverage) {
280 std::error_code EC;
281 raw_fd_ostream OS(FileName, EC, sys::fs::OF_None);
282 failIfError(Error: EC);
283
284 OS.write(Ptr: reinterpret_cast<const char *>(&Coverage.Header),
285 Size: sizeof(Coverage.Header));
286
287 switch (Coverage.Header.Bitness) {
288 case Bitness64:
289 for (auto Addr : *Coverage.Addrs) {
290 uint64_t Addr64 = Addr;
291 OS.write(Ptr: reinterpret_cast<const char *>(&Addr64), Size: sizeof(Addr64));
292 }
293 break;
294 case Bitness32:
295 for (auto Addr : *Coverage.Addrs) {
296 uint32_t Addr32 = static_cast<uint32_t>(Addr);
297 OS.write(Ptr: reinterpret_cast<const char *>(&Addr32), Size: sizeof(Addr32));
298 }
299 break;
300 default:
301 fail(E: "Unsupported bitness: " + std::to_string(val: Coverage.Header.Bitness));
302 }
303}
304
305static raw_ostream &operator<<(raw_ostream &OS, const CoverageStats &Stats) {
306 OS << "all-edges: " << Stats.AllPoints << "\n";
307 OS << "cov-edges: " << Stats.CovPoints << "\n";
308 OS << "all-functions: " << Stats.AllFns << "\n";
309 OS << "cov-functions: " << Stats.CovFns << "\n";
310 return OS;
311}
312
313// Output symbolized information for coverage points in JSON.
314// Format:
315// {
316// '<file_name>' : {
317// '<function_name>' : {
318// '<point_id'> : '<line_number>:'<column_number'.
319// ....
320// }
321// }
322// }
323static void operator<<(json::OStream &W,
324 const std::vector<CoveragePoint> &Points) {
325 // Group points by file.
326 std::map<std::string, std::vector<const CoveragePoint *>> PointsByFile;
327 for (const auto &Point : Points) {
328 for (const DILineInfo &Loc : Point.Locs) {
329 PointsByFile[Loc.FileName].push_back(x: &Point);
330 }
331 }
332
333 for (const auto &P : PointsByFile) {
334 std::string FileName = P.first;
335 std::map<std::string, std::vector<const CoveragePoint *>> PointsByFn;
336 for (auto PointPtr : P.second) {
337 for (const DILineInfo &Loc : PointPtr->Locs) {
338 PointsByFn[Loc.FunctionName].push_back(x: PointPtr);
339 }
340 }
341
342 W.attributeObject(Key: P.first, Contents: [&] {
343 // Group points by function.
344 for (const auto &P : PointsByFn) {
345 std::string FunctionName = P.first;
346 std::set<std::string> WrittenIds;
347
348 W.attributeObject(Key: FunctionName, Contents: [&] {
349 for (const CoveragePoint *Point : P.second) {
350 for (const auto &Loc : Point->Locs) {
351 if (Loc.FileName != FileName || Loc.FunctionName != FunctionName)
352 continue;
353 if (!WrittenIds.insert(x: Point->Id).second)
354 continue;
355
356 // Output <point_id> : "<line>:<col>".
357 W.attribute(Key: Point->Id,
358 Contents: (utostr(X: Loc.Line) + ":" + utostr(X: Loc.Column)));
359 }
360 }
361 });
362 }
363 });
364 }
365}
366
367static void operator<<(json::OStream &W, const SymbolizedCoverage &C) {
368 W.object(Contents: [&] {
369 W.attributeArray(Key: "covered-points", Contents: [&] {
370 for (const std::string &P : C.CoveredIds) {
371 W.value(V: P);
372 }
373 });
374 W.attribute(Key: "binary-hash", Contents: C.BinaryHash);
375 W.attributeObject(Key: "point-symbol-info", Contents: [&] { W << C.Points; });
376 });
377}
378
379static std::string parseScalarString(yaml::Node *N) {
380 SmallString<64> StringStorage;
381 yaml::ScalarNode *S = dyn_cast_if_present<yaml::ScalarNode>(Val: N);
382 failIf(B: !S, E: "expected string");
383 return std::string(S->getValue(Storage&: StringStorage));
384}
385
386std::unique_ptr<SymbolizedCoverage>
387SymbolizedCoverage::read(const std::string &InputFile) {
388 auto Coverage(std::make_unique<SymbolizedCoverage>());
389
390 std::map<std::string, CoveragePoint> Points;
391 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
392 MemoryBuffer::getFile(Filename: InputFile);
393 failIfError(E: BufOrErr);
394
395 SourceMgr SM;
396 yaml::Stream S(**BufOrErr, SM);
397
398 yaml::document_iterator DI = S.begin();
399 failIf(B: DI == S.end(), E: "empty document: " + InputFile);
400 yaml::Node *Root = DI->getRoot();
401 failIf(B: !Root, E: "expecting root node: " + InputFile);
402 yaml::MappingNode *Top = dyn_cast<yaml::MappingNode>(Val: Root);
403 failIf(B: !Top, E: "expecting mapping node: " + InputFile);
404
405 for (auto &KVNode : *Top) {
406 auto Key = parseScalarString(N: KVNode.getKey());
407
408 if (Key == "covered-points") {
409 yaml::SequenceNode *Points =
410 dyn_cast_if_present<yaml::SequenceNode>(Val: KVNode.getValue());
411 failIf(B: !Points, E: "expected array: " + InputFile);
412
413 for (auto I = Points->begin(), E = Points->end(); I != E; ++I) {
414 Coverage->CoveredIds.insert(x: parseScalarString(N: &*I));
415 }
416 } else if (Key == "binary-hash") {
417 Coverage->BinaryHash = parseScalarString(N: KVNode.getValue());
418 } else if (Key == "point-symbol-info") {
419 yaml::MappingNode *PointSymbolInfo =
420 dyn_cast_if_present<yaml::MappingNode>(Val: KVNode.getValue());
421 failIf(B: !PointSymbolInfo, E: "expected mapping node: " + InputFile);
422
423 for (auto &FileKVNode : *PointSymbolInfo) {
424 auto Filename = parseScalarString(N: FileKVNode.getKey());
425
426 yaml::MappingNode *FileInfo =
427 dyn_cast_if_present<yaml::MappingNode>(Val: FileKVNode.getValue());
428 failIf(B: !FileInfo, E: "expected mapping node: " + InputFile);
429
430 for (auto &FunctionKVNode : *FileInfo) {
431 auto FunctionName = parseScalarString(N: FunctionKVNode.getKey());
432
433 yaml::MappingNode *FunctionInfo =
434 dyn_cast_if_present<yaml::MappingNode>(Val: FunctionKVNode.getValue());
435 failIf(B: !FunctionInfo, E: "expected mapping node: " + InputFile);
436
437 for (auto &PointKVNode : *FunctionInfo) {
438 auto PointId = parseScalarString(N: PointKVNode.getKey());
439 auto Loc = parseScalarString(N: PointKVNode.getValue());
440
441 size_t ColonPos = Loc.find(c: ':');
442 failIf(B: ColonPos == std::string::npos, E: "expected ':': " + InputFile);
443
444 auto LineStr = Loc.substr(pos: 0, n: ColonPos);
445 auto ColStr = Loc.substr(pos: ColonPos + 1, n: Loc.size());
446
447 DILineInfo LineInfo;
448 LineInfo.FileName = Filename;
449 LineInfo.FunctionName = FunctionName;
450 char *End;
451 LineInfo.Line = std::strtoul(nptr: LineStr.c_str(), endptr: &End, base: 10);
452 LineInfo.Column = std::strtoul(nptr: ColStr.c_str(), endptr: &End, base: 10);
453
454 CoveragePoint *CoveragePoint =
455 &Points.try_emplace(k: PointId, args&: PointId).first->second;
456 CoveragePoint->Locs.push_back(Elt: LineInfo);
457 }
458 }
459 }
460 } else {
461 errs() << "Ignoring unknown key: " << Key << "\n";
462 }
463 }
464
465 for (auto &KV : Points) {
466 Coverage->Points.push_back(x: KV.second);
467 }
468
469 return Coverage;
470}
471
472// ---------- MAIN FUNCTIONALITY ----------
473
474std::string stripPathPrefix(std::string Path) {
475 if (ClStripPathPrefix.empty())
476 return Path;
477 size_t Pos = Path.find(str: ClStripPathPrefix);
478 if (Pos == std::string::npos)
479 return Path;
480 return Path.substr(pos: Pos + ClStripPathPrefix.size());
481}
482
483static std::unique_ptr<symbolize::LLVMSymbolizer> createSymbolizer() {
484 symbolize::LLVMSymbolizer::Options SymbolizerOptions;
485 SymbolizerOptions.Demangle = ClDemangle;
486 SymbolizerOptions.UseSymbolTable = true;
487 return std::make_unique<symbolize::LLVMSymbolizer>(args&: SymbolizerOptions);
488}
489
490static std::string normalizeFilename(const std::string &FileName) {
491 SmallString<256> S(FileName);
492 sys::path::remove_dots(path&: S, /* remove_dot_dot */ true);
493 return stripPathPrefix(Path: sys::path::convert_to_slash(path: std::string(S)));
494}
495
496class Ignorelists {
497public:
498 Ignorelists()
499 : DefaultIgnorelist(createDefaultIgnorelist()),
500 UserIgnorelist(createUserIgnorelist()) {}
501
502 bool isIgnorelisted(const DILineInfo &I) {
503 if (DefaultIgnorelist &&
504 DefaultIgnorelist->inSection(Section: "sancov", Prefix: "fun", Query: I.FunctionName))
505 return true;
506 if (DefaultIgnorelist &&
507 DefaultIgnorelist->inSection(Section: "sancov", Prefix: "src", Query: I.FileName))
508 return true;
509 if (UserIgnorelist &&
510 UserIgnorelist->inSection(Section: "sancov", Prefix: "fun", Query: I.FunctionName))
511 return true;
512 if (UserIgnorelist &&
513 UserIgnorelist->inSection(Section: "sancov", Prefix: "src", Query: I.FileName))
514 return true;
515 return false;
516 }
517
518private:
519 static std::unique_ptr<SpecialCaseList> createDefaultIgnorelist() {
520 if (!ClUseDefaultIgnorelist)
521 return std::unique_ptr<SpecialCaseList>();
522 std::unique_ptr<MemoryBuffer> MB =
523 MemoryBuffer::getMemBuffer(InputData: DefaultIgnorelistStr);
524 std::string Error;
525 auto Ignorelist = SpecialCaseList::create(MB: MB.get(), Error);
526 failIfNotEmpty(E: Error);
527 return Ignorelist;
528 }
529
530 static std::unique_ptr<SpecialCaseList> createUserIgnorelist() {
531 if (ClIgnorelist.empty())
532 return std::unique_ptr<SpecialCaseList>();
533 return SpecialCaseList::createOrDie(Paths: {{ClIgnorelist}},
534 FS&: *vfs::getRealFileSystem());
535 }
536 std::unique_ptr<SpecialCaseList> DefaultIgnorelist;
537 std::unique_ptr<SpecialCaseList> UserIgnorelist;
538};
539
540static std::vector<CoveragePoint>
541getCoveragePoints(const std::string &ObjectFile,
542 const std::set<uint64_t> &Addrs,
543 const std::set<uint64_t> &CoveredAddrs) {
544 std::vector<CoveragePoint> Result;
545 auto Symbolizer(createSymbolizer());
546 Ignorelists Ig;
547
548 std::set<std::string> CoveredFiles;
549 if (ClSkipDeadFiles) {
550 for (auto Addr : CoveredAddrs) {
551 // TODO: it would be neccessary to set proper section index here.
552 // object::SectionedAddress::UndefSection works for only absolute
553 // addresses.
554 object::SectionedAddress ModuleAddress = {
555 .Address: Addr, .SectionIndex: object::SectionedAddress::UndefSection};
556
557 auto LineInfo = Symbolizer->symbolizeCode(ModuleName: ObjectFile, ModuleOffset: ModuleAddress);
558 failIfError(E&: LineInfo);
559 CoveredFiles.insert(x: LineInfo->FileName);
560 auto InliningInfo =
561 Symbolizer->symbolizeInlinedCode(ModuleName: ObjectFile, ModuleOffset: ModuleAddress);
562 failIfError(E&: InliningInfo);
563 for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {
564 auto FrameInfo = InliningInfo->getFrame(Index: I);
565 CoveredFiles.insert(x: FrameInfo.FileName);
566 }
567 }
568 }
569
570 for (auto Addr : Addrs) {
571 std::set<DILineInfo> Infos; // deduplicate debug info.
572
573 // TODO: it would be neccessary to set proper section index here.
574 // object::SectionedAddress::UndefSection works for only absolute addresses.
575 object::SectionedAddress ModuleAddress = {
576 .Address: Addr, .SectionIndex: object::SectionedAddress::UndefSection};
577
578 auto LineInfo = Symbolizer->symbolizeCode(ModuleName: ObjectFile, ModuleOffset: ModuleAddress);
579 failIfError(E&: LineInfo);
580 if (ClSkipDeadFiles &&
581 CoveredFiles.find(x: LineInfo->FileName) == CoveredFiles.end())
582 continue;
583 LineInfo->FileName = normalizeFilename(FileName: LineInfo->FileName);
584 if (Ig.isIgnorelisted(I: *LineInfo))
585 continue;
586
587 auto Id = utohexstr(X: Addr, LowerCase: true);
588 auto Point = CoveragePoint(Id);
589 Infos.insert(x: *LineInfo);
590 Point.Locs.push_back(Elt: *LineInfo);
591
592 auto InliningInfo =
593 Symbolizer->symbolizeInlinedCode(ModuleName: ObjectFile, ModuleOffset: ModuleAddress);
594 failIfError(E&: InliningInfo);
595 for (uint32_t I = 0; I < InliningInfo->getNumberOfFrames(); ++I) {
596 auto FrameInfo = InliningInfo->getFrame(Index: I);
597 if (ClSkipDeadFiles &&
598 CoveredFiles.find(x: FrameInfo.FileName) == CoveredFiles.end())
599 continue;
600 FrameInfo.FileName = normalizeFilename(FileName: FrameInfo.FileName);
601 if (Ig.isIgnorelisted(I: FrameInfo))
602 continue;
603 if (Infos.insert(x: FrameInfo).second)
604 Point.Locs.push_back(Elt: FrameInfo);
605 }
606
607 Result.push_back(x: Point);
608 }
609
610 return Result;
611}
612
613static bool isCoveragePointSymbol(StringRef Name) {
614 return Name == "__sanitizer_cov" || Name == "__sanitizer_cov_with_check" ||
615 Name == "__sanitizer_cov_trace_func_enter" ||
616 Name == "__sanitizer_cov_trace_pc_guard" ||
617 // Mac has '___' prefix
618 Name == "___sanitizer_cov" || Name == "___sanitizer_cov_with_check" ||
619 Name == "___sanitizer_cov_trace_func_enter" ||
620 Name == "___sanitizer_cov_trace_pc_guard" ||
621 // Large Aarch64 binaries use thunks
622 Name == "__AArch64ADRPThunk___sanitizer_cov" ||
623 Name == "__AArch64ADRPThunk___sanitizer_cov_with_check" ||
624 Name == "__AArch64ADRPThunk___sanitizer_cov_trace_func_enter" ||
625 Name == "__AArch64ADRPThunk___sanitizer_cov_trace_pc_guard";
626}
627
628// Locate __sanitizer_cov* function addresses inside the stubs table on MachO.
629static void findMachOIndirectCovFunctions(const object::MachOObjectFile &O,
630 std::set<uint64_t> *Result) {
631 MachO::dysymtab_command Dysymtab = O.getDysymtabLoadCommand();
632 MachO::symtab_command Symtab = O.getSymtabLoadCommand();
633
634 for (const auto &Load : O.load_commands()) {
635 if (Load.C.cmd == MachO::LC_SEGMENT_64) {
636 MachO::segment_command_64 Seg = O.getSegment64LoadCommand(L: Load);
637 for (unsigned J = 0; J < Seg.nsects; ++J) {
638 MachO::section_64 Sec = O.getSection64(L: Load, Index: J);
639
640 uint32_t SectionType = Sec.flags & MachO::SECTION_TYPE;
641 if (SectionType == MachO::S_SYMBOL_STUBS) {
642 uint32_t Stride = Sec.reserved2;
643 uint32_t Cnt = Sec.size / Stride;
644 uint32_t N = Sec.reserved1;
645 for (uint32_t J = 0; J < Cnt && N + J < Dysymtab.nindirectsyms; J++) {
646 uint32_t IndirectSymbol =
647 O.getIndirectSymbolTableEntry(DLC: Dysymtab, Index: N + J);
648 uint64_t Addr = Sec.addr + J * Stride;
649 if (IndirectSymbol < Symtab.nsyms) {
650 object::SymbolRef Symbol = *(O.getSymbolByIndex(Index: IndirectSymbol));
651 Expected<StringRef> Name = Symbol.getName();
652 failIfError(E&: Name);
653 if (isCoveragePointSymbol(Name: Name.get())) {
654 Result->insert(x: Addr);
655 }
656 }
657 }
658 }
659 }
660 }
661 if (Load.C.cmd == MachO::LC_SEGMENT) {
662 errs() << "ERROR: 32 bit MachO binaries not supported\n";
663 }
664 }
665}
666
667// Locate __sanitizer_cov* function addresses that are used for coverage
668// reporting.
669static std::set<uint64_t>
670findSanitizerCovFunctions(const object::ObjectFile &O) {
671 std::set<uint64_t> Result;
672
673 for (const object::SymbolRef &Symbol : O.symbols()) {
674 Expected<uint64_t> AddressOrErr = Symbol.getAddress();
675 failIfError(E&: AddressOrErr);
676 uint64_t Address = AddressOrErr.get();
677
678 Expected<StringRef> NameOrErr = Symbol.getName();
679 failIfError(E&: NameOrErr);
680 StringRef Name = NameOrErr.get();
681
682 Expected<uint32_t> FlagsOrErr = Symbol.getFlags();
683 // TODO: Test this error.
684 failIfError(E&: FlagsOrErr);
685 uint32_t Flags = FlagsOrErr.get();
686
687 // XCOFF uses "." prefix for function entry point symbols.
688 StringRef EffectiveName =
689 (isa<object::XCOFFObjectFile>(Val: &O) && Name.starts_with(Prefix: "."))
690 ? Name.drop_front(N: 1)
691 : Name;
692 if (!(Flags & object::BasicSymbolRef::SF_Undefined) &&
693 isCoveragePointSymbol(Name: EffectiveName)) {
694 Result.insert(x: Address);
695 }
696 }
697
698 if (const auto *CO = dyn_cast<object::COFFObjectFile>(Val: &O)) {
699 for (const object::ExportDirectoryEntryRef &Export :
700 CO->export_directories()) {
701 uint32_t RVA;
702 failIfError(Err: Export.getExportRVA(Result&: RVA));
703
704 StringRef Name;
705 failIfError(Err: Export.getSymbolName(Result&: Name));
706
707 if (isCoveragePointSymbol(Name))
708 Result.insert(x: CO->getImageBase() + RVA);
709 }
710 }
711
712 if (const auto *MO = dyn_cast<object::MachOObjectFile>(Val: &O)) {
713 findMachOIndirectCovFunctions(O: *MO, Result: &Result);
714 }
715
716 return Result;
717}
718
719// Ported from
720// compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h:GetPreviousInstructionPc
721// GetPreviousInstructionPc.
722static uint64_t getPreviousInstructionPc(uint64_t PC, Triple TheTriple) {
723 if (TheTriple.isARM())
724 return (PC - 3) & (~1);
725 if (TheTriple.isMIPS() || TheTriple.isSPARC())
726 return PC - 8;
727 if (TheTriple.isRISCV())
728 return PC - 2;
729 if (TheTriple.isX86() || TheTriple.isSystemZ())
730 return PC - 1;
731 return PC - 4;
732}
733
734// Locate addresses of all coverage points in a file. Coverage point
735// is defined as the 'address of instruction following __sanitizer_cov
736// call - 1'.
737static void getObjectCoveragePoints(const object::ObjectFile &O,
738 std::set<uint64_t> *Addrs) {
739 Triple TheTriple("unknown-unknown-unknown");
740 TheTriple.setArch(Kind: Triple::ArchType(O.getArch()));
741 auto TripleName = TheTriple.getTriple();
742
743 std::string Error;
744 const Target *TheTarget = TargetRegistry::lookupTarget(TheTriple, Error);
745 failIfNotEmpty(E: Error);
746
747 std::unique_ptr<const MCSubtargetInfo> STI(
748 TheTarget->createMCSubtargetInfo(TheTriple, CPU: "", Features: ""));
749 failIfEmpty(Ptr: STI, Message: "no subtarget info for target " + TripleName);
750
751 std::unique_ptr<const MCRegisterInfo> MRI(
752 TheTarget->createMCRegInfo(TT: TheTriple));
753 failIfEmpty(Ptr: MRI, Message: "no register info for target " + TripleName);
754
755 MCTargetOptions MCOptions;
756 std::unique_ptr<const MCAsmInfo> AsmInfo(
757 TheTarget->createMCAsmInfo(MRI: *MRI, TheTriple, Options: MCOptions));
758 failIfEmpty(Ptr: AsmInfo, Message: "no asm info for target " + TripleName);
759
760 MCContext Ctx(TheTriple, *AsmInfo, *MRI, *STI);
761 std::unique_ptr<MCDisassembler> DisAsm(
762 TheTarget->createMCDisassembler(STI: *STI, Ctx));
763 failIfEmpty(Ptr: DisAsm, Message: "no disassembler info for target " + TripleName);
764
765 std::unique_ptr<const MCInstrInfo> MII(TheTarget->createMCInstrInfo());
766 failIfEmpty(Ptr: MII, Message: "no instruction info for target " + TripleName);
767
768 std::unique_ptr<MCInstrAnalysis> MIA(
769 TheTarget->createMCInstrAnalysis(Info: MII.get()));
770 failIfEmpty(Ptr: MIA, Message: "no instruction analysis info for target " + TripleName);
771
772 auto SanCovAddrs = findSanitizerCovFunctions(O);
773 if (SanCovAddrs.empty())
774 fail(E: "__sanitizer_cov* functions not found");
775
776 for (object::SectionRef Section : O.sections()) {
777 if (Section.isVirtual() || !Section.isText()) // llvm-objdump does the same.
778 continue;
779 uint64_t SectionAddr = Section.getAddress();
780 uint64_t SectSize = Section.getSize();
781 if (!SectSize)
782 continue;
783
784 Expected<StringRef> BytesStr = Section.getContents();
785 failIfError(E&: BytesStr);
786 ArrayRef<uint8_t> Bytes = arrayRefFromStringRef(Input: *BytesStr);
787
788 if (MIA)
789 MIA->resetState();
790
791 for (uint64_t Index = 0, Size = 0; Index < Section.getSize();
792 Index += Size) {
793 MCInst Inst;
794 ArrayRef<uint8_t> ThisBytes = Bytes.slice(N: Index);
795 uint64_t ThisAddr = SectionAddr + Index;
796 if (!DisAsm->getInstruction(Instr&: Inst, Size, Bytes: ThisBytes, Address: ThisAddr, CStream&: nulls())) {
797 if (Size == 0)
798 Size = std::min<uint64_t>(
799 a: ThisBytes.size(),
800 b: DisAsm->suggestBytesToSkip(Bytes: ThisBytes, Address: ThisAddr));
801 MIA->resetState();
802 continue;
803 }
804 uint64_t Addr = Index + SectionAddr;
805 // Sanitizer coverage uses the address of the next instruction - 1.
806 uint64_t CovPoint = getPreviousInstructionPc(PC: Addr + Size, TheTriple);
807 uint64_t Target;
808 if (MIA->isCall(Inst) &&
809 MIA->evaluateBranch(Inst, Addr: SectionAddr + Index, Size, Target) &&
810 SanCovAddrs.find(x: Target) != SanCovAddrs.end())
811 Addrs->insert(x: CovPoint);
812 MIA->updateState(Inst, STI: STI.get(), Addr);
813 }
814 }
815}
816
817static void
818visitObjectFiles(const object::Archive &A,
819 function_ref<void(const object::ObjectFile &)> Fn) {
820 Error Err = Error::success();
821 for (auto &C : A.children(Err)) {
822 Expected<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary();
823 failIfError(E&: ChildOrErr);
824 if (auto *O = dyn_cast<object::ObjectFile>(Val: &*ChildOrErr.get()))
825 Fn(*O);
826 else
827 failIfError(Error: object::object_error::invalid_file_type);
828 }
829 failIfError(Err: std::move(Err));
830}
831
832static void
833visitObjectFiles(const std::string &FileName,
834 function_ref<void(const object::ObjectFile &)> Fn) {
835 Expected<object::OwningBinary<object::Binary>> BinaryOrErr =
836 object::createBinary(Path: FileName);
837 if (!BinaryOrErr)
838 failIfError(E&: BinaryOrErr);
839
840 object::Binary &Binary = *BinaryOrErr.get().getBinary();
841 if (object::Archive *A = dyn_cast<object::Archive>(Val: &Binary))
842 visitObjectFiles(A: *A, Fn);
843 else if (object::ObjectFile *O = dyn_cast<object::ObjectFile>(Val: &Binary))
844 Fn(*O);
845 else
846 failIfError(Error: object::object_error::invalid_file_type);
847}
848
849static std::set<uint64_t>
850findSanitizerCovFunctions(const std::string &FileName) {
851 std::set<uint64_t> Result;
852 visitObjectFiles(FileName, Fn: [&](const object::ObjectFile &O) {
853 auto Addrs = findSanitizerCovFunctions(O);
854 Result.insert(first: Addrs.begin(), last: Addrs.end());
855 });
856 return Result;
857}
858
859// Locate addresses of all coverage points in a file. Coverage point
860// is defined as the 'address of instruction following __sanitizer_cov
861// call - 1'.
862static std::set<uint64_t> findCoveragePointAddrs(const std::string &FileName) {
863 std::set<uint64_t> Result;
864 visitObjectFiles(FileName, Fn: [&](const object::ObjectFile &O) {
865 getObjectCoveragePoints(O, Addrs: &Result);
866 });
867 return Result;
868}
869
870static void printCovPoints(const std::string &ObjFile, raw_ostream &OS) {
871 for (uint64_t Addr : findCoveragePointAddrs(FileName: ObjFile)) {
872 OS << "0x";
873 OS.write_hex(N: Addr);
874 OS << "\n";
875 }
876}
877
878static ErrorOr<bool> isCoverageFile(const std::string &FileName) {
879 auto ShortFileName = llvm::sys::path::filename(path: FileName);
880 if (!SancovFileRegex.match(String: ShortFileName))
881 return false;
882
883 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
884 MemoryBuffer::getFile(Filename: FileName);
885 if (!BufOrErr) {
886 errs() << "Warning: " << BufOrErr.getError().message() << "("
887 << BufOrErr.getError().value()
888 << "), filename: " << llvm::sys::path::filename(path: FileName) << "\n";
889 return BufOrErr.getError();
890 }
891 std::unique_ptr<MemoryBuffer> Buf = std::move(BufOrErr.get());
892 if (Buf->getBufferSize() < 8) {
893 return false;
894 }
895 const FileHeader *Header =
896 reinterpret_cast<const FileHeader *>(Buf->getBufferStart());
897 return Header->Magic == BinCoverageMagic;
898}
899
900static bool isSymbolizedCoverageFile(const std::string &FileName) {
901 auto ShortFileName = llvm::sys::path::filename(path: FileName);
902 return SymcovFileRegex.match(String: ShortFileName);
903}
904
905static std::unique_ptr<SymbolizedCoverage>
906symbolize(const RawCoverage &Data, const std::string ObjectFile) {
907 auto Coverage = std::make_unique<SymbolizedCoverage>();
908
909 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
910 MemoryBuffer::getFile(Filename: ObjectFile);
911 failIfError(E: BufOrErr);
912 SHA1 Hasher;
913 Hasher.update(Str: (*BufOrErr)->getBuffer());
914 Coverage->BinaryHash = toHex(Input: Hasher.final());
915
916 Ignorelists Ig;
917 auto Symbolizer(createSymbolizer());
918
919 for (uint64_t Addr : *Data.Addrs) {
920 // TODO: it would be neccessary to set proper section index here.
921 // object::SectionedAddress::UndefSection works for only absolute addresses.
922 auto LineInfo = Symbolizer->symbolizeCode(
923 ModuleName: ObjectFile, ModuleOffset: {.Address: Addr, .SectionIndex: object::SectionedAddress::UndefSection});
924 failIfError(E&: LineInfo);
925 if (Ig.isIgnorelisted(I: *LineInfo))
926 continue;
927
928 Coverage->CoveredIds.insert(x: utohexstr(X: Addr, LowerCase: true));
929 }
930
931 std::set<uint64_t> AllAddrs = findCoveragePointAddrs(FileName: ObjectFile);
932 if (!llvm::includes(Range1&: AllAddrs, Range2&: *Data.Addrs)) {
933 fail(E: "Coverage points in binary and .sancov file do not match.");
934 }
935 Coverage->Points = getCoveragePoints(ObjectFile, Addrs: AllAddrs, CoveredAddrs: *Data.Addrs);
936 return Coverage;
937}
938
939struct FileFn {
940 bool operator<(const FileFn &RHS) const {
941 return std::tie(args: FileName, args: FunctionName) <
942 std::tie(args: RHS.FileName, args: RHS.FunctionName);
943 }
944
945 std::string FileName;
946 std::string FunctionName;
947};
948
949static std::set<FileFn>
950computeFunctions(const std::vector<CoveragePoint> &Points) {
951 std::set<FileFn> Fns;
952 for (const auto &Point : Points) {
953 for (const auto &Loc : Point.Locs) {
954 Fns.insert(x: FileFn{.FileName: Loc.FileName, .FunctionName: Loc.FunctionName});
955 }
956 }
957 return Fns;
958}
959
960static std::set<FileFn>
961computeNotCoveredFunctions(const SymbolizedCoverage &Coverage) {
962 auto Fns = computeFunctions(Points: Coverage.Points);
963
964 for (const auto &Point : Coverage.Points) {
965 if (Coverage.CoveredIds.find(x: Point.Id) == Coverage.CoveredIds.end())
966 continue;
967
968 for (const auto &Loc : Point.Locs) {
969 Fns.erase(x: FileFn{.FileName: Loc.FileName, .FunctionName: Loc.FunctionName});
970 }
971 }
972
973 return Fns;
974}
975
976static std::set<FileFn>
977computeCoveredFunctions(const SymbolizedCoverage &Coverage) {
978 std::set<FileFn> Result;
979
980 for (const auto &Point : Coverage.Points) {
981 if (Coverage.CoveredIds.find(x: Point.Id) == Coverage.CoveredIds.end())
982 continue;
983
984 for (const auto &Loc : Point.Locs) {
985 Result.insert(x: FileFn{.FileName: Loc.FileName, .FunctionName: Loc.FunctionName});
986 }
987 }
988
989 return Result;
990}
991
992typedef std::map<FileFn, std::pair<uint32_t, uint32_t>> FunctionLocs;
993// finds first location in a file for each function.
994static FunctionLocs resolveFunctions(const SymbolizedCoverage &Coverage,
995 const std::set<FileFn> &Fns) {
996 FunctionLocs Result;
997 for (const auto &Point : Coverage.Points) {
998 for (const auto &Loc : Point.Locs) {
999 FileFn Fn = FileFn{.FileName: Loc.FileName, .FunctionName: Loc.FunctionName};
1000 if (Fns.find(x: Fn) == Fns.end())
1001 continue;
1002
1003 auto P = std::make_pair(x: Loc.Line, y: Loc.Column);
1004 auto [It, Inserted] = Result.try_emplace(k: Fn, args&: P);
1005 if (!Inserted && It->second > P)
1006 It->second = P;
1007 }
1008 }
1009 return Result;
1010}
1011
1012static void printFunctionLocs(const FunctionLocs &FnLocs, raw_ostream &OS) {
1013 for (const auto &P : FnLocs) {
1014 OS << stripPathPrefix(Path: P.first.FileName) << ":" << P.second.first << " "
1015 << P.first.FunctionName << "\n";
1016 }
1017}
1018CoverageStats computeStats(const SymbolizedCoverage &Coverage) {
1019 CoverageStats Stats = {.AllPoints: Coverage.Points.size(), .CovPoints: Coverage.CoveredIds.size(),
1020 .AllFns: computeFunctions(Points: Coverage.Points).size(),
1021 .CovFns: computeCoveredFunctions(Coverage).size()};
1022 return Stats;
1023}
1024
1025// Print list of covered functions.
1026// Line format: <file_name>:<line> <function_name>
1027static void printCoveredFunctions(const SymbolizedCoverage &CovData,
1028 raw_ostream &OS) {
1029 auto CoveredFns = computeCoveredFunctions(Coverage: CovData);
1030 printFunctionLocs(FnLocs: resolveFunctions(Coverage: CovData, Fns: CoveredFns), OS);
1031}
1032
1033// Print list of not covered functions.
1034// Line format: <file_name>:<line> <function_name>
1035static void printNotCoveredFunctions(const SymbolizedCoverage &CovData,
1036 raw_ostream &OS) {
1037 auto NotCoveredFns = computeNotCoveredFunctions(Coverage: CovData);
1038 printFunctionLocs(FnLocs: resolveFunctions(Coverage: CovData, Fns: NotCoveredFns), OS);
1039}
1040
1041// Read list of files and merges their coverage info.
1042static void readAndPrintRawCoverage(const std::vector<std::string> &FileNames,
1043 raw_ostream &OS) {
1044 for (const auto &FileName : FileNames) {
1045 auto Cov = RawCoverage::read(FileName);
1046 if (!Cov)
1047 continue;
1048 OS << *Cov.get();
1049 }
1050}
1051
1052static const char *bitnessToString(uint32_t Bitness) {
1053 switch (Bitness) {
1054 case Bitness64:
1055 return "64-bit";
1056 case Bitness32:
1057 return "32-bit";
1058 default:
1059 fail(E: "Unsupported bitness: " + std::to_string(val: Bitness));
1060 return nullptr;
1061 }
1062}
1063
1064// Warn if two file headers have different bitness.
1065static void warnIfDifferentBitness(const FileHeader &Header1,
1066 const FileHeader &Header2,
1067 const std::string &File1Desc,
1068 const std::string &File2Desc) {
1069 if (Header1.Bitness != Header2.Bitness) {
1070 errs() << "WARNING: Input files have different bitness (" << File1Desc
1071 << ": " << bitnessToString(Bitness: Header1.Bitness) << ", " << File2Desc
1072 << ": " << bitnessToString(Bitness: Header2.Bitness)
1073 << "). Using bitness from " << File1Desc << ".\n";
1074
1075 if (Header1.Bitness == Bitness32 && Header2.Bitness == Bitness64) {
1076 errs() << "WARNING: 64-bit addresses will be truncated to 32 bits. "
1077 << "This may result in data loss.\n";
1078 }
1079 }
1080}
1081
1082// Compute difference between two coverage files (A - B) and write to output
1083// file.
1084static void diffRawCoverage(const std::string &FileA, const std::string &FileB,
1085 const std::string &OutputFile) {
1086 auto CovA = RawCoverage::read(FileName: FileA);
1087 failIfError(E: CovA);
1088
1089 auto CovB = RawCoverage::read(FileName: FileB);
1090 failIfError(E: CovB);
1091
1092 const FileHeader &HeaderA = CovA.get()->Header;
1093 const FileHeader &HeaderB = CovB.get()->Header;
1094
1095 warnIfDifferentBitness(Header1: HeaderA, Header2: HeaderB, File1Desc: FileA, File2Desc: FileB);
1096
1097 // Compute A - B
1098 auto DiffAddrs = std::make_unique<std::set<uint64_t>>();
1099 std::set_difference(first1: CovA.get()->Addrs->begin(), last1: CovA.get()->Addrs->end(),
1100 first2: CovB.get()->Addrs->begin(), last2: CovB.get()->Addrs->end(),
1101 result: std::inserter(x&: *DiffAddrs, i: DiffAddrs->end()));
1102
1103 RawCoverage DiffCov(std::move(DiffAddrs), HeaderA);
1104 RawCoverage::write(FileName: OutputFile, Coverage: DiffCov);
1105}
1106
1107// Compute union of multiple coverage files and write to output file.
1108static void unionRawCoverage(const std::vector<std::string> &InputFiles,
1109 const std::string &OutputFile) {
1110 failIf(B: InputFiles.empty(), E: "union action requires at least one input file");
1111
1112 // Read the first file to get the header and initial coverage
1113 auto UnionCov = RawCoverage::read(FileName: InputFiles[0]);
1114 failIfError(E: UnionCov);
1115
1116 const FileHeader &UnionHeader = UnionCov.get()->Header;
1117
1118 for (size_t I = 1; I < InputFiles.size(); ++I) {
1119 auto Cov = RawCoverage::read(FileName: InputFiles[I]);
1120 failIfError(E: Cov);
1121
1122 const FileHeader &CurHeader = Cov.get()->Header;
1123
1124 warnIfDifferentBitness(Header1: UnionHeader, Header2: CurHeader, File1Desc: InputFiles[0],
1125 File2Desc: InputFiles[I]);
1126
1127 UnionCov.get()->Addrs->insert(first: Cov.get()->Addrs->begin(),
1128 last: Cov.get()->Addrs->end());
1129 }
1130
1131 RawCoverage::write(FileName: OutputFile, Coverage: *UnionCov.get());
1132}
1133
1134static std::unique_ptr<SymbolizedCoverage>
1135merge(const std::vector<std::unique_ptr<SymbolizedCoverage>> &Coverages) {
1136 if (Coverages.empty())
1137 return nullptr;
1138
1139 auto Result = std::make_unique<SymbolizedCoverage>();
1140
1141 for (size_t I = 0; I < Coverages.size(); ++I) {
1142 const SymbolizedCoverage &Coverage = *Coverages[I];
1143 std::string Prefix;
1144 if (Coverages.size() > 1) {
1145 // prefix is not needed when there's only one file.
1146 Prefix = utostr(X: I);
1147 }
1148
1149 for (const auto &Id : Coverage.CoveredIds) {
1150 Result->CoveredIds.insert(x: Prefix + Id);
1151 }
1152
1153 for (const auto &CovPoint : Coverage.Points) {
1154 CoveragePoint NewPoint(CovPoint);
1155 NewPoint.Id = Prefix + CovPoint.Id;
1156 Result->Points.push_back(x: NewPoint);
1157 }
1158 }
1159
1160 if (Coverages.size() == 1) {
1161 Result->BinaryHash = Coverages[0]->BinaryHash;
1162 }
1163
1164 return Result;
1165}
1166
1167static std::unique_ptr<SymbolizedCoverage>
1168readSymbolizeAndMergeCmdArguments(std::vector<std::string> FileNames) {
1169 std::vector<std::unique_ptr<SymbolizedCoverage>> Coverages;
1170
1171 {
1172 // Short name => file name.
1173 std::map<std::string, std::string, std::less<>> ObjFiles;
1174 std::string FirstObjFile;
1175 std::set<std::string> CovFiles;
1176
1177 // Partition input values into coverage/object files.
1178 for (const auto &FileName : FileNames) {
1179 if (isSymbolizedCoverageFile(FileName)) {
1180 Coverages.push_back(x: SymbolizedCoverage::read(InputFile: FileName));
1181 }
1182
1183 auto ErrorOrIsCoverage = isCoverageFile(FileName);
1184 if (!ErrorOrIsCoverage)
1185 continue;
1186 if (ErrorOrIsCoverage.get()) {
1187 CovFiles.insert(x: FileName);
1188 } else {
1189 auto ShortFileName = llvm::sys::path::filename(path: FileName);
1190 if (ObjFiles.find(x: ShortFileName) != ObjFiles.end()) {
1191 fail(E: "Duplicate binary file with a short name: " + ShortFileName);
1192 }
1193
1194 ObjFiles[std::string(ShortFileName)] = FileName;
1195 if (FirstObjFile.empty())
1196 FirstObjFile = FileName;
1197 }
1198 }
1199
1200 SmallVector<StringRef, 2> Components;
1201
1202 // Object file => list of corresponding coverage file names.
1203 std::map<std::string, std::vector<std::string>> CoverageByObjFile;
1204 for (const auto &FileName : CovFiles) {
1205 auto ShortFileName = llvm::sys::path::filename(path: FileName);
1206 auto Ok = SancovFileRegex.match(String: ShortFileName, Matches: &Components);
1207 if (!Ok) {
1208 fail(E: "Can't match coverage file name against "
1209 "<module_name>.<pid>.sancov pattern: " +
1210 FileName);
1211 }
1212
1213 auto Iter = ObjFiles.find(x: Components[1]);
1214 if (Iter == ObjFiles.end()) {
1215 fail(E: "Object file for coverage not found: " + FileName);
1216 }
1217
1218 CoverageByObjFile[Iter->second].push_back(x: FileName);
1219 };
1220
1221 for (const auto &Pair : ObjFiles) {
1222 auto FileName = Pair.second;
1223 if (CoverageByObjFile.find(x: FileName) == CoverageByObjFile.end())
1224 errs() << "WARNING: No coverage file for " << FileName << "\n";
1225 }
1226
1227 // Read raw coverage and symbolize it.
1228 for (const auto &Pair : CoverageByObjFile) {
1229 if (findSanitizerCovFunctions(FileName: Pair.first).empty()) {
1230 errs()
1231 << "WARNING: Ignoring " << Pair.first
1232 << " and its coverage because __sanitizer_cov* functions were not "
1233 "found.\n";
1234 continue;
1235 }
1236
1237 for (const std::string &CoverageFile : Pair.second) {
1238 auto DataOrError = RawCoverage::read(FileName: CoverageFile);
1239 failIfError(E: DataOrError);
1240 Coverages.push_back(x: symbolize(Data: *DataOrError.get(), ObjectFile: Pair.first));
1241 }
1242 }
1243 }
1244
1245 return merge(Coverages);
1246}
1247
1248} // namespace
1249
1250static void parseArgs(int Argc, char **Argv) {
1251 SancovOptTable Tbl;
1252 llvm::BumpPtrAllocator A;
1253 llvm::StringSaver Saver{A};
1254 opt::InputArgList Args =
1255 Tbl.parseArgs(Argc, Argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
1256 llvm::errs() << Msg << '\n';
1257 std::exit(status: 1);
1258 });
1259
1260 if (Args.hasArg(Ids: OPT_help)) {
1261 Tbl.printHelp(
1262 OS&: llvm::outs(),
1263 Usage: "sancov [options] <action> <binary files...> <.sancov files...> "
1264 "<.symcov files...>",
1265 Title: "Sanitizer Coverage Processing Tool (sancov)\n\n"
1266 " This tool can extract various coverage-related information from: \n"
1267 " coverage-instrumented binary files, raw .sancov files and their "
1268 "symbolized .symcov version.\n"
1269 " Depending on chosen action the tool expects different input files:\n"
1270 " -print-coverage-pcs - coverage-instrumented binary files\n"
1271 " -print-coverage - .sancov files\n"
1272 " -diff - two .sancov files & --output option\n"
1273 " -union - one or more .sancov files & --output "
1274 "option\n"
1275 " <other actions> - .sancov files & corresponding binary "
1276 "files, .symcov files\n");
1277 std::exit(status: 0);
1278 }
1279
1280 if (Args.hasArg(Ids: OPT_version)) {
1281 cl::PrintVersionMessage();
1282 std::exit(status: 0);
1283 }
1284
1285 if (Args.hasMultipleArgs(Id: OPT_action_grp)) {
1286 fail(E: "Only one action option is allowed");
1287 }
1288
1289 for (const opt::Arg *A : Args.filtered(Ids: OPT_INPUT)) {
1290 ClInputFiles.emplace_back(args: A->getValue());
1291 }
1292
1293 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_action_grp)) {
1294 switch (A->getOption().getID()) {
1295 case OPT_print:
1296 Action = ActionType::PrintAction;
1297 break;
1298 case OPT_diff:
1299 Action = ActionType::DiffAction;
1300 break;
1301 case OPT_union_files:
1302 Action = ActionType::UnionAction;
1303 break;
1304 case OPT_printCoveragePcs:
1305 Action = ActionType::PrintCovPointsAction;
1306 break;
1307 case OPT_coveredFunctions:
1308 Action = ActionType::CoveredFunctionsAction;
1309 break;
1310 case OPT_notCoveredFunctions:
1311 Action = ActionType::NotCoveredFunctionsAction;
1312 break;
1313 case OPT_printCoverageStats:
1314 Action = ActionType::StatsAction;
1315 break;
1316 case OPT_htmlReport:
1317 Action = ActionType::HtmlReportAction;
1318 break;
1319 case OPT_symbolize:
1320 Action = ActionType::SymbolizeAction;
1321 break;
1322 case OPT_merge:
1323 Action = ActionType::MergeAction;
1324 break;
1325 default:
1326 fail(E: "Invalid Action");
1327 }
1328 }
1329
1330 ClDemangle = Args.hasFlag(Pos: OPT_demangle, Neg: OPT_no_demangle, Default: true);
1331 ClSkipDeadFiles = Args.hasFlag(Pos: OPT_skipDeadFiles, Neg: OPT_no_skipDeadFiles, Default: true);
1332 ClUseDefaultIgnorelist =
1333 Args.hasFlag(Pos: OPT_useDefaultIgnoreList, Neg: OPT_no_useDefaultIgnoreList, Default: true);
1334
1335 ClStripPathPrefix = Args.getLastArgValue(Id: OPT_stripPathPrefix_EQ);
1336 ClIgnorelist = Args.getLastArgValue(Id: OPT_ignorelist_EQ);
1337 ClOutputFile = Args.getLastArgValue(Id: OPT_output_EQ);
1338}
1339
1340int sancov_main(int Argc, char **Argv, const llvm::ToolContext &) {
1341 llvm::InitializeAllTargetInfos();
1342 llvm::InitializeAllTargetMCs();
1343 llvm::InitializeAllDisassemblers();
1344
1345 parseArgs(Argc, Argv);
1346
1347 // -print doesn't need object files.
1348 if (Action == PrintAction) {
1349 readAndPrintRawCoverage(FileNames: ClInputFiles, OS&: outs());
1350 return 0;
1351 }
1352 if (Action == DiffAction) {
1353 // -diff requires exactly 2 input files and an output file.
1354 failIf(B: ClInputFiles.size() != 2,
1355 E: "diff action requires exactly 2 input sancov files");
1356 failIf(
1357 B: ClOutputFile.empty(),
1358 E: "diff action requires --output option to specify output sancov file");
1359 diffRawCoverage(FileA: ClInputFiles[0], FileB: ClInputFiles[1], OutputFile: ClOutputFile);
1360 return 0;
1361 }
1362 if (Action == UnionAction) {
1363 // -union requires at least 1 input file and an output file.
1364 failIf(B: ClInputFiles.empty(),
1365 E: "union action requires at least one input sancov file");
1366 failIf(
1367 B: ClOutputFile.empty(),
1368 E: "union action requires --output option to specify output sancov file");
1369 unionRawCoverage(InputFiles: ClInputFiles, OutputFile: ClOutputFile);
1370 return 0;
1371 }
1372 if (Action == PrintCovPointsAction) {
1373 // -print-coverage-points doesn't need coverage files.
1374 for (const std::string &ObjFile : ClInputFiles) {
1375 printCovPoints(ObjFile, OS&: outs());
1376 }
1377 return 0;
1378 }
1379
1380 auto Coverage = readSymbolizeAndMergeCmdArguments(FileNames: ClInputFiles);
1381 failIf(B: !Coverage, E: "No valid coverage files given.");
1382
1383 switch (Action) {
1384 case CoveredFunctionsAction: {
1385 printCoveredFunctions(CovData: *Coverage, OS&: outs());
1386 return 0;
1387 }
1388 case NotCoveredFunctionsAction: {
1389 printNotCoveredFunctions(CovData: *Coverage, OS&: outs());
1390 return 0;
1391 }
1392 case StatsAction: {
1393 outs() << computeStats(Coverage: *Coverage);
1394 return 0;
1395 }
1396 case MergeAction:
1397 case SymbolizeAction: { // merge & symbolize are synonims.
1398 json::OStream W(outs(), 2);
1399 W << *Coverage;
1400 return 0;
1401 }
1402 case HtmlReportAction:
1403 errs() << "-html-report option is removed: "
1404 "use -symbolize & coverage-report-server.py instead\n";
1405 return 1;
1406 case DiffAction:
1407 case UnionAction:
1408 case PrintAction:
1409 case PrintCovPointsAction:
1410 llvm_unreachable("unsupported action");
1411 }
1412
1413 return 0;
1414}
1415