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