[Coverage] Load code coverage data from archives

Support loading code coverage data from regular archives, thin archives,
and from MachO universal binaries which contain archives.

Testing: check-llvm, check-profile (with {A,UB}San enabled)

rdar://51538999

Differential Revision: https://reviews.llvm.org/D63232

llvm-svn: 363325
This commit is contained in:
Vedant Kumar 2019-06-13 20:48:57 +00:00
parent 7d8d84a0ca
commit 9f5b410e3f
11 changed files with 204 additions and 73 deletions

View File

@ -181,6 +181,9 @@ The :program:`llvm-cov show` command shows line by line coverage of the
binaries *BIN*,... using the profile data *PROFILE*. It can optionally be
filtered to only show the coverage for the files listed in *SOURCES*.
*BIN* may be an executable, object file, dynamic library, or archive (thin or
otherwise).
To use :program:`llvm-cov show`, you need a program that is compiled with
instrumentation to emit profile and coverage data. To build such a program with
``clang`` use the ``-fprofile-instr-generate`` and ``-fcoverage-mapping``
@ -331,6 +334,9 @@ The :program:`llvm-cov report` command displays a summary of the coverage of
the binaries *BIN*,... using the profile data *PROFILE*. It can optionally be
filtered to only show the coverage for the files listed in *SOURCES*.
*BIN* may be an executable, object file, dynamic library, or archive (thin or
otherwise).
If no source files are provided, a summary line is printed for each file in the
coverage data. If any files are provided, summaries can be shown for each
function in the listed files if the ``-show-functions`` option is enabled.

View File

@ -203,9 +203,15 @@ public:
BinaryCoverageReader(const BinaryCoverageReader &) = delete;
BinaryCoverageReader &operator=(const BinaryCoverageReader &) = delete;
static Expected<std::vector<std::unique_ptr<BinaryCoverageReader>>>
create(MemoryBufferRef ObjectBuffer, StringRef Arch,
SmallVectorImpl<std::unique_ptr<MemoryBuffer>> &ObjectFileBuffers);
static Expected<std::unique_ptr<BinaryCoverageReader>>
create(std::unique_ptr<MemoryBuffer> &ObjectBuffer,
StringRef Arch);
createCoverageReaderFromBuffer(StringRef Coverage,
InstrProfSymtab &&ProfileNames,
uint8_t BytesInAddress,
support::endianness Endian);
Error readNextRecord(CoverageMappingRecord &Record) override;
};

View File

@ -285,11 +285,14 @@ CoverageMapping::load(ArrayRef<StringRef> ObjectFilenames,
if (std::error_code EC = CovMappingBufOrErr.getError())
return errorCodeToError(EC);
StringRef Arch = Arches.empty() ? StringRef() : Arches[File.index()];
auto CoverageReaderOrErr =
BinaryCoverageReader::create(CovMappingBufOrErr.get(), Arch);
if (Error E = CoverageReaderOrErr.takeError())
MemoryBufferRef CovMappingBufRef =
CovMappingBufOrErr.get()->getMemBufferRef();
auto CoverageReadersOrErr =
BinaryCoverageReader::create(CovMappingBufRef, Arch, Buffers);
if (Error E = CoverageReadersOrErr.takeError())
return std::move(E);
Readers.push_back(std::move(CoverageReaderOrErr.get()));
for (auto &Reader : CoverageReadersOrErr.get())
Readers.push_back(std::move(Reader));
Buffers.push_back(std::move(CovMappingBufOrErr.get()));
}
return load(Readers, *ProfileReader);

View File

@ -586,12 +586,43 @@ static Error readCoverageMappingData(
static const char *TestingFormatMagic = "llvmcovmtestdata";
static Error loadTestingFormat(StringRef Data, InstrProfSymtab &ProfileNames,
StringRef &CoverageMapping,
uint8_t &BytesInAddress,
support::endianness &Endian) {
BytesInAddress = 8;
Endian = support::endianness::little;
Expected<std::unique_ptr<BinaryCoverageReader>>
BinaryCoverageReader::createCoverageReaderFromBuffer(
StringRef Coverage, InstrProfSymtab &&ProfileNames, uint8_t BytesInAddress,
support::endianness Endian) {
std::unique_ptr<BinaryCoverageReader> Reader(new BinaryCoverageReader());
Reader->ProfileNames = std::move(ProfileNames);
if (BytesInAddress == 4 && Endian == support::endianness::little) {
if (Error E =
readCoverageMappingData<uint32_t, support::endianness::little>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames))
return std::move(E);
} else if (BytesInAddress == 4 && Endian == support::endianness::big) {
if (Error E = readCoverageMappingData<uint32_t, support::endianness::big>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames))
return std::move(E);
} else if (BytesInAddress == 8 && Endian == support::endianness::little) {
if (Error E =
readCoverageMappingData<uint64_t, support::endianness::little>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames))
return std::move(E);
} else if (BytesInAddress == 8 && Endian == support::endianness::big) {
if (Error E = readCoverageMappingData<uint64_t, support::endianness::big>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames))
return std::move(E);
} else
return make_error<CoverageMapError>(coveragemap_error::malformed);
return Reader;
}
static Expected<std::unique_ptr<BinaryCoverageReader>>
loadTestingFormat(StringRef Data) {
uint8_t BytesInAddress = 8;
support::endianness Endian = support::endianness::little;
Data = Data.substr(StringRef(TestingFormatMagic).size());
if (Data.empty())
@ -610,9 +641,10 @@ static Error loadTestingFormat(StringRef Data, InstrProfSymtab &ProfileNames,
Data = Data.substr(N);
if (Data.size() < ProfileNamesSize)
return make_error<CoverageMapError>(coveragemap_error::malformed);
InstrProfSymtab ProfileNames;
if (Error E = ProfileNames.create(Data.substr(0, ProfileNamesSize), Address))
return E;
CoverageMapping = Data.substr(ProfileNamesSize);
return std::move(E);
StringRef CoverageMapping = Data.substr(ProfileNamesSize);
// Skip the padding bytes because coverage map data has an alignment of 8.
if (CoverageMapping.empty())
return make_error<CoverageMapError>(coveragemap_error::truncated);
@ -620,7 +652,8 @@ static Error loadTestingFormat(StringRef Data, InstrProfSymtab &ProfileNames,
if (CoverageMapping.size() < Pad)
return make_error<CoverageMapError>(coveragemap_error::malformed);
CoverageMapping = CoverageMapping.substr(Pad);
return Error::success();
return BinaryCoverageReader::createCoverageReaderFromBuffer(
CoverageMapping, std::move(ProfileNames), BytesInAddress, Endian);
}
static Expected<SectionRef> lookupSection(ObjectFile &OF, StringRef Name) {
@ -643,15 +676,8 @@ static Expected<SectionRef> lookupSection(ObjectFile &OF, StringRef Name) {
return make_error<CoverageMapError>(coveragemap_error::no_data_found);
}
static Error loadBinaryFormat(MemoryBufferRef ObjectBuffer,
InstrProfSymtab &ProfileNames,
StringRef &CoverageMapping,
uint8_t &BytesInAddress,
support::endianness &Endian, StringRef Arch) {
auto BinOrErr = createBinary(ObjectBuffer);
if (!BinOrErr)
return BinOrErr.takeError();
auto Bin = std::move(BinOrErr.get());
static Expected<std::unique_ptr<BinaryCoverageReader>>
loadBinaryFormat(std::unique_ptr<Binary> Bin, StringRef Arch) {
std::unique_ptr<ObjectFile> OF;
if (auto *Universal = dyn_cast<MachOUniversalBinary>(Bin.get())) {
// If we have a universal binary, try to look up the object for the
@ -671,9 +697,10 @@ static Error loadBinaryFormat(MemoryBufferRef ObjectBuffer,
return make_error<CoverageMapError>(coveragemap_error::malformed);
// The coverage uses native pointer sizes for the object it's written in.
BytesInAddress = OF->getBytesInAddress();
Endian = OF->isLittleEndian() ? support::endianness::little
: support::endianness::big;
uint8_t BytesInAddress = OF->getBytesInAddress();
support::endianness Endian = OF->isLittleEndian()
? support::endianness::little
: support::endianness::big;
// Look for the sections that we are interested in.
auto ObjFormat = OF->getTripleObjectFormat();
@ -681,66 +708,101 @@ static Error loadBinaryFormat(MemoryBufferRef ObjectBuffer,
lookupSection(*OF, getInstrProfSectionName(IPSK_name, ObjFormat,
/*AddSegmentInfo=*/false));
if (auto E = NamesSection.takeError())
return E;
return std::move(E);
auto CoverageSection =
lookupSection(*OF, getInstrProfSectionName(IPSK_covmap, ObjFormat,
/*AddSegmentInfo=*/false));
if (auto E = CoverageSection.takeError())
return E;
return std::move(E);
// Get the contents of the given sections.
if (Expected<StringRef> E = CoverageSection->getContents())
CoverageMapping = *E;
else
return E.takeError();
auto CoverageMappingOrErr = CoverageSection->getContents();
if (!CoverageMappingOrErr)
return CoverageMappingOrErr.takeError();
InstrProfSymtab ProfileNames;
if (Error E = ProfileNames.create(*NamesSection))
return E;
return std::move(E);
return Error::success();
return BinaryCoverageReader::createCoverageReaderFromBuffer(
CoverageMappingOrErr.get(), std::move(ProfileNames), BytesInAddress,
Endian);
}
Expected<std::unique_ptr<BinaryCoverageReader>>
BinaryCoverageReader::create(std::unique_ptr<MemoryBuffer> &ObjectBuffer,
StringRef Arch) {
std::unique_ptr<BinaryCoverageReader> Reader(new BinaryCoverageReader());
Expected<std::vector<std::unique_ptr<BinaryCoverageReader>>>
BinaryCoverageReader::create(
MemoryBufferRef ObjectBuffer, StringRef Arch,
SmallVectorImpl<std::unique_ptr<MemoryBuffer>> &ObjectFileBuffers) {
std::vector<std::unique_ptr<BinaryCoverageReader>> Readers;
StringRef Coverage;
uint8_t BytesInAddress;
support::endianness Endian;
Error E = Error::success();
consumeError(std::move(E));
if (ObjectBuffer->getBuffer().startswith(TestingFormatMagic))
if (ObjectBuffer.getBuffer().startswith(TestingFormatMagic)) {
// This is a special format used for testing.
E = loadTestingFormat(ObjectBuffer->getBuffer(), Reader->ProfileNames,
Coverage, BytesInAddress, Endian);
else
E = loadBinaryFormat(ObjectBuffer->getMemBufferRef(), Reader->ProfileNames,
Coverage, BytesInAddress, Endian, Arch);
if (E)
return std::move(E);
auto ReaderOrErr = loadTestingFormat(ObjectBuffer.getBuffer());
if (!ReaderOrErr)
return ReaderOrErr.takeError();
Readers.push_back(std::move(ReaderOrErr.get()));
return Readers;
}
if (BytesInAddress == 4 && Endian == support::endianness::little)
E = readCoverageMappingData<uint32_t, support::endianness::little>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames);
else if (BytesInAddress == 4 && Endian == support::endianness::big)
E = readCoverageMappingData<uint32_t, support::endianness::big>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames);
else if (BytesInAddress == 8 && Endian == support::endianness::little)
E = readCoverageMappingData<uint64_t, support::endianness::little>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames);
else if (BytesInAddress == 8 && Endian == support::endianness::big)
E = readCoverageMappingData<uint64_t, support::endianness::big>(
Reader->ProfileNames, Coverage, Reader->MappingRecords,
Reader->Filenames);
else
return make_error<CoverageMapError>(coveragemap_error::malformed);
if (E)
return std::move(E);
return std::move(Reader);
auto BinOrErr = createBinary(ObjectBuffer);
if (!BinOrErr)
return BinOrErr.takeError();
std::unique_ptr<Binary> Bin = std::move(BinOrErr.get());
// MachO universal binaries which contain archives need to be treated as
// archives, not as regular binaries.
if (auto *Universal = dyn_cast<MachOUniversalBinary>(Bin.get())) {
for (auto &ObjForArch : Universal->objects()) {
// Skip slices within the universal binary which target the wrong arch.
std::string ObjArch = ObjForArch.getArchFlagName();
if (Arch != ObjArch)
continue;
auto ArchiveOrErr = ObjForArch.getAsArchive();
if (!ArchiveOrErr) {
// If this is not an archive, try treating it as a regular object.
consumeError(ArchiveOrErr.takeError());
break;
}
return BinaryCoverageReader::create(
ArchiveOrErr.get()->getMemoryBufferRef(), Arch, ObjectFileBuffers);
}
}
// Load coverage out of archive members.
if (auto *Ar = dyn_cast<Archive>(Bin.get())) {
Error Err = Error::success();
for (auto &Child : Ar->children(Err)) {
Expected<MemoryBufferRef> ChildBufOrErr = Child.getMemoryBufferRef();
if (!ChildBufOrErr)
return ChildBufOrErr.takeError();
auto ChildReadersOrErr = BinaryCoverageReader::create(
ChildBufOrErr.get(), Arch, ObjectFileBuffers);
if (!ChildReadersOrErr)
return ChildReadersOrErr.takeError();
for (auto &Reader : ChildReadersOrErr.get())
Readers.push_back(std::move(Reader));
}
if (Err)
return std::move(Err);
// Thin archives reference object files outside of the archive file, i.e.
// files which reside in memory not owned by the caller. Transfer ownership
// to the caller.
if (Ar->isThin())
for (auto &Buffer : Ar->takeThinBuffers())
ObjectFileBuffers.push_back(std::move(Buffer));
return Readers;
}
auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch);
if (!ReaderOrErr)
return ReaderOrErr.takeError();
Readers.push_back(std::move(ReaderOrErr.get()));
return Readers;
}
Error BinaryCoverageReader::readNextRecord(CoverageMappingRecord &Record) {

View File

@ -0,0 +1 @@
void f1() {}

View File

@ -0,0 +1 @@
void f2() {}

View File

@ -0,0 +1,8 @@
f1
0x0
1
100
f2
0x0
1
100

View File

@ -0,0 +1,44 @@
The coverage reader should be able to handle archives, and archives embedded within
MachO universal binaries.
---
Steps to re-generate these files on macOS:
clang -fprofile-instr-generate -fcoverage-mapping -c obj1.c -o obj1_32.o -arch i386
clang -fprofile-instr-generate -fcoverage-mapping -c obj2.c -o obj2_32.o -arch i386
clang -fprofile-instr-generate -fcoverage-mapping -c obj1.c -o obj1_64.o -arch x86_64
clang -fprofile-instr-generate -fcoverage-mapping -c obj2.c -o obj2_64.o -arch x86_64
ar -q archive_32 obj1_32.o obj2_32.o
ar -q archive_64 obj1_64.o obj2_64.o
lipo -output universal_bin_wrapping_archives -create archive_32 archive_64
---
RUN: llvm-profdata merge %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives.proftext -o %t.profdata
RUN: llvm-cov show %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \
RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch i386 \
RUN: | FileCheck %s --check-prefix=SHOW_ARCHIVE
RUN: llvm-cov show %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \
RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch x86_64 \
RUN: | FileCheck %s --check-prefix=SHOW_ARCHIVE
SHOW_ARCHIVE: {{.*}}obj1.c:
SHOW_ARCHIVE-NEXT: 1| 100|void f1() {}
SHOW_ARCHIVE: {{.*}}obj2.c:
SHOW_ARCHIVE-NEXT: 1| 100|void f2() {}
RUN: llvm-cov report %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \
RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch i386 \
RUN: | FileCheck %s --check-prefix=REPORT_ARCHIVE
RUN: llvm-cov report %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \
RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch x86_64 \
RUN: | FileCheck %s --check-prefix=REPORT_ARCHIVE
RUN: llvm-ar rcT %t.thin32.a %S/Inputs/universal_bin_wrapping_archives/obj1_32.o %S/Inputs/universal_bin_wrapping_archives/obj2_32.o
RUN: llvm-cov report %t.thin32.a -instr-profile %t.profdata | FileCheck %s --check-prefix=REPORT_ARCHIVE
REPORT_ARCHIVE: obj1.c 1 0 100.00% 1 0 100.00% 1 0 100.00%
REPORT_ARCHIVE: obj2.c 1 0 100.00% 1 0 100.00% 1 0 100.00%
REPORT_ARCHIVE: TOTAL 2 0 100.00% 2 0 100.00% 2 0 100.00%