mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2025-01-09 13:21:30 +00:00
[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:
parent
7d8d84a0ca
commit
9f5b410e3f
@ -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.
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -0,0 +1 @@
|
||||
void f1() {}
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
void f2() {}
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,8 @@
|
||||
f1
|
||||
0x0
|
||||
1
|
||||
100
|
||||
f2
|
||||
0x0
|
||||
1
|
||||
100
|
44
test/tools/llvm-cov/universal_bin_wrapping_archives.test
Normal file
44
test/tools/llvm-cov/universal_bin_wrapping_archives.test
Normal 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%
|
Loading…
Reference in New Issue
Block a user