Skip to content

Commit 57cb2f6

Browse files
yhgu2000gulfemsavrun
authored andcommitted
Reland "[llvm-cov] Support multi-source object files for convert-for-testing"
`llvm-cov convert-for-testing` only functions properly when the input binary contains a single source file. When the binary has multiple source files, a `Malformed coverage data` error will occur when the generated .covmapping is read back. This is because the testing format lacks support for indicating the size of its file records, and current implementation just assumes there's only one record in it. This patch fixes this problem by introducing a new testing format version. Changes to the code: - Add a new format version. The version number is stored in the the last 8 bytes of the orignial magic number field to be backward-compatible. - Output a LEB128 number before the file records section to indicate its size in the new version. - Change the format parsing code correspondingly. - Update the document to formalize the testing format. - Additionally, fix the bug when converting COFF binaries. Reviewed By: phosek, gulfem Differential Revision: https://reviews.llvm.org/D156611
1 parent f631a10 commit 57cb2f6

File tree

7 files changed

+255
-51
lines changed

7 files changed

+255
-51
lines changed

llvm/docs/CommandGuide/llvm-cov.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,3 +543,25 @@ OPTIONS
543543

544544
Fail if an object file cannot be found for a binary ID present in the profile,
545545
neither on the command line nor via binary ID lookup.
546+
547+
CONVERT-FOR-TESTING COMMAND
548+
---------------------------
549+
550+
.. warning::
551+
This command is for the LLVM developers who are working on ``llvm-cov`` only.
552+
553+
SYNOPSIS
554+
^^^^^^^^
555+
556+
:program:`llvm-cov convert-for-testing` *BIN* -o *OUT*
557+
558+
DESCRIPTION
559+
^^^^^^^^^^^
560+
561+
The :program:`llvm-cov convert-for-testing` command serves the purpose of
562+
testing `llvm-cov` itself. It can extract all code coverage data from the
563+
binary *BIN* to the file *OUT*, thereby reducing the size of test files. The
564+
output file typically bears the :program:`.covmapping` extension.
565+
566+
The :program:`.covmapping` files can be read back by ``llvm-cov`` just as
567+
ordinary binary files.

llvm/docs/CoverageMappingFormat.rst

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,15 @@ In version 2, the function record for *foo* was defined as follows:
318318
Coverage Mapping Header:
319319
------------------------
320320

321-
The coverage mapping header has the following fields:
321+
As shown above, the coverage mapping header has the following fields:
322322

323323
* The number of function records affixed to the coverage header. Always 0, but present for backwards compatibility.
324324

325325
* The length of the string in the third field of *__llvm_coverage_mapping* that contains the encoded translation unit filenames.
326326

327327
* The length of the string in the third field of *__llvm_coverage_mapping* that contains any encoded coverage mapping data affixed to the coverage header. Always 0, but present for backwards compatibility.
328328

329-
* The format version. The current version is 4 (encoded as a 3).
329+
* The format version. The current version is 6 (encoded as a 5).
330330

331331
.. _function records:
332332

@@ -610,3 +610,70 @@ The source range record contains the following fields:
610610
* *columnEnd*: The ending column of the mapping region. If the high bit is set,
611611
the current mapping region is a gap area. A count for a gap area is only used
612612
as the line execution count if there are no other regions on a line.
613+
614+
Testing Format
615+
==============
616+
617+
.. warning::
618+
This section is for the LLVM developers who are working on ``llvm-cov`` only.
619+
620+
``llvm-cov`` uses a special file format (called ``.covmapping`` below) for
621+
testing purposes. This format is private and should have no use for general
622+
users. As a developer, you can get such files by the ``convert-for-testing``
623+
subcommand of ``llvm-cov``.
624+
625+
The structure of the ``.covmapping`` files follows:
626+
627+
``[magicNumber : u64, version : u64, profileNames, coverageMapping, coverageRecords]``
628+
629+
Magic Number and Version
630+
------------------------
631+
632+
The magic is ``0x6d766f636d766c6c``, which is the ASCII string
633+
``llvmcovm`` in little-endian.
634+
635+
There are two versions for now:
636+
637+
- Version1, encoded as ``0x6174616474736574`` (ASCII string ``testdata``).
638+
- Version2, encoded as 1.
639+
640+
The only difference between Version1 and Version2 is in the encoding of the
641+
``coverageMapping`` fields, which is explained later.
642+
643+
Profile Names
644+
------------
645+
646+
``profileNames``, ``coverageMapping`` and ``coverageRecords`` are 3 sections
647+
extracted from the original binary file.
648+
649+
``profileNames`` encodes the size, address and the raw data of the section:
650+
651+
``[profileNamesSize : LEB128, profileNamesAddr : LEB128, profileNamesData : bytes]``
652+
653+
Coverage Mapping
654+
---------------
655+
656+
This field is padded with zero bytes to make it 8-byte aligned.
657+
658+
``coverageMapping`` contains the records of the source files. In version 1,
659+
only one record is stored:
660+
661+
``[padding : bytes, coverageMappingData : bytes]``
662+
663+
Version 2 relaxes this restriction by encoding the size of
664+
``coverageMappingData`` as a LEB128 number before the data:
665+
666+
``[coverageMappingSize : LEB128, padding : bytes, coverageMappingData : bytes]``
667+
668+
The current version is 2.
669+
670+
Coverage Records
671+
---------------
672+
673+
This field is padded with zero bytes to make it 8-byte aligned.
674+
675+
``coverageRecords`` is encoded as:
676+
677+
``[padding : bytes, coverageRecordsData : bytes]``
678+
679+
The rest data in the file is considered as the ``coverageRecordsData``.

llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,20 @@ enum CovMapVersion {
10271027
CurrentVersion = INSTR_PROF_COVMAP_VERSION
10281028
};
10291029

1030+
// Correspond to "llvmcovm", in little-endian.
1031+
constexpr uint64_t TestingFormatMagic = 0x6d766f636d766c6c;
1032+
1033+
enum class TestingFormatVersion : uint64_t {
1034+
// The first version's number corresponds to the string "testdata" in
1035+
// little-endian. This is for a historical reason.
1036+
Version1 = 0x6174616474736574,
1037+
// Version1 has a defect that it can't store multiple file records. Version2
1038+
// fix this problem by adding a new field before the file records section.
1039+
Version2 = 1,
1040+
// The current testing format version is Version2.
1041+
CurrentVersion = Version2
1042+
};
1043+
10301044
template <int CovMapVersion, class IntPtrT> struct CovMapTraits {
10311045
using CovMapFuncRecordType = CovMapFunctionRecordV3;
10321046
using NameRefType = uint64_t;

llvm/include/llvm/ProfileData/Coverage/CoverageMappingWriter.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ class CoverageMappingWriter {
5454
void write(raw_ostream &OS);
5555
};
5656

57+
/// Writer for the coverage mapping testing format.
58+
class TestingFormatWriter {
59+
uint64_t ProfileNamesAddr;
60+
StringRef ProfileNamesData;
61+
StringRef CoverageMappingData;
62+
StringRef CoverageRecordsData;
63+
64+
public:
65+
TestingFormatWriter(uint64_t ProfileNamesAddr, StringRef ProfileNamesData,
66+
StringRef CoverageMappingData,
67+
StringRef CoverageRecordsData)
68+
: ProfileNamesAddr(ProfileNamesAddr), ProfileNamesData(ProfileNamesData),
69+
CoverageMappingData(CoverageMappingData),
70+
CoverageRecordsData(CoverageRecordsData) {}
71+
72+
/// Encode to the given output stream.
73+
void
74+
write(raw_ostream &OS,
75+
TestingFormatVersion Version = TestingFormatVersion::CurrentVersion);
76+
};
77+
5778
} // end namespace coverage
5879

5980
} // end namespace llvm

llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -822,8 +822,6 @@ static Error readCoverageMappingData(
822822
return Error::success();
823823
}
824824

825-
static const char *TestingFormatMagic = "llvmcovmtestdata";
826-
827825
Expected<std::unique_ptr<BinaryCoverageReader>>
828826
BinaryCoverageReader::createCoverageReaderFromBuffer(
829827
StringRef Coverage, FuncRecordsStorage &&FuncRecords,
@@ -865,7 +863,16 @@ loadTestingFormat(StringRef Data, StringRef CompilationDir) {
865863
uint8_t BytesInAddress = 8;
866864
support::endianness Endian = support::endianness::little;
867865

868-
Data = Data.substr(StringRef(TestingFormatMagic).size());
866+
// Read the magic and version.
867+
Data = Data.substr(sizeof(TestingFormatMagic));
868+
if (Data.size() < sizeof(uint64_t))
869+
return make_error<CoverageMapError>(coveragemap_error::malformed);
870+
auto TestingVersion =
871+
support::endian::byte_swap<uint64_t, support::endianness::little>(
872+
*reinterpret_cast<const uint64_t *>(Data.data()));
873+
Data = Data.substr(sizeof(uint64_t));
874+
875+
// Read the ProfileNames data.
869876
if (Data.empty())
870877
return make_error<CoverageMapError>(coveragemap_error::truncated);
871878
unsigned N = 0;
@@ -886,41 +893,62 @@ loadTestingFormat(StringRef Data, StringRef CompilationDir) {
886893
if (Error E = ProfileNames.create(Data.substr(0, ProfileNamesSize), Address))
887894
return std::move(E);
888895
Data = Data.substr(ProfileNamesSize);
896+
897+
// In Version2, the size of CoverageMapping is stored directly.
898+
uint64_t CoverageMappingSize;
899+
if (TestingVersion == uint64_t(TestingFormatVersion::Version2)) {
900+
N = 0;
901+
CoverageMappingSize = decodeULEB128(Data.bytes_begin(), &N);
902+
if (N > Data.size())
903+
return make_error<CoverageMapError>(coveragemap_error::malformed);
904+
Data = Data.substr(N);
905+
if (CoverageMappingSize < sizeof(CovMapHeader))
906+
return make_error<CoverageMapError>(coveragemap_error::malformed);
907+
} else if (TestingVersion != uint64_t(TestingFormatVersion::Version1)) {
908+
return make_error<CoverageMapError>(coveragemap_error::unsupported_version);
909+
}
910+
889911
// Skip the padding bytes because coverage map data has an alignment of 8.
890-
size_t Pad = offsetToAlignedAddr(Data.data(), Align(8));
912+
auto Pad = offsetToAlignedAddr(Data.data(), Align(8));
891913
if (Data.size() < Pad)
892914
return make_error<CoverageMapError>(coveragemap_error::malformed);
893915
Data = Data.substr(Pad);
894916
if (Data.size() < sizeof(CovMapHeader))
895917
return make_error<CoverageMapError>(coveragemap_error::malformed);
896918
auto const *CovHeader = reinterpret_cast<const CovMapHeader *>(
897919
Data.substr(0, sizeof(CovMapHeader)).data());
898-
CovMapVersion Version =
899-
(CovMapVersion)CovHeader->getVersion<support::endianness::little>();
900-
StringRef CoverageMapping;
901-
BinaryCoverageReader::FuncRecordsStorage CoverageRecords;
920+
auto Version =
921+
CovMapVersion(CovHeader->getVersion<support::endianness::little>());
922+
923+
// In Version1, the size of CoverageMapping is calculated.
924+
if (TestingVersion == uint64_t(TestingFormatVersion::Version1)) {
925+
if (Version < CovMapVersion::Version4) {
926+
CoverageMappingSize = Data.size();
927+
} else {
928+
auto FilenamesSize =
929+
CovHeader->getFilenamesSize<support::endianness::little>();
930+
CoverageMappingSize = sizeof(CovMapHeader) + FilenamesSize;
931+
}
932+
}
933+
934+
auto CoverageMapping = Data.substr(0, CoverageMappingSize);
935+
Data = Data.substr(CoverageMappingSize);
936+
937+
// Read the CoverageRecords data.
902938
if (Version < CovMapVersion::Version4) {
903-
CoverageMapping = Data;
904-
if (CoverageMapping.empty())
905-
return make_error<CoverageMapError>(coveragemap_error::truncated);
906-
CoverageRecords = MemoryBuffer::getMemBuffer("");
939+
if (!Data.empty())
940+
return make_error<CoverageMapError>(coveragemap_error::malformed);
907941
} else {
908-
uint32_t FilenamesSize =
909-
CovHeader->getFilenamesSize<support::endianness::little>();
910-
uint32_t CoverageMappingSize = sizeof(CovMapHeader) + FilenamesSize;
911-
CoverageMapping = Data.substr(0, CoverageMappingSize);
912-
if (CoverageMapping.empty())
913-
return make_error<CoverageMapError>(coveragemap_error::truncated);
914-
Data = Data.substr(CoverageMappingSize);
915942
// Skip the padding bytes because coverage records data has an alignment
916943
// of 8.
917944
Pad = offsetToAlignedAddr(Data.data(), Align(8));
918945
if (Data.size() < Pad)
919946
return make_error<CoverageMapError>(coveragemap_error::malformed);
920-
CoverageRecords = MemoryBuffer::getMemBuffer(Data.substr(Pad));
921-
if (CoverageRecords->getBufferSize() == 0)
922-
return make_error<CoverageMapError>(coveragemap_error::truncated);
947+
Data = Data.substr(Pad);
923948
}
949+
BinaryCoverageReader::FuncRecordsStorage CoverageRecords =
950+
MemoryBuffer::getMemBuffer(Data);
951+
924952
return BinaryCoverageReader::createCoverageReaderFromBuffer(
925953
CoverageMapping, std::move(CoverageRecords), std::move(ProfileNames),
926954
BytesInAddress, Endian, CompilationDir);
@@ -1081,14 +1109,19 @@ BinaryCoverageReader::create(
10811109
StringRef CompilationDir, SmallVectorImpl<object::BuildIDRef> *BinaryIDs) {
10821110
std::vector<std::unique_ptr<BinaryCoverageReader>> Readers;
10831111

1084-
if (ObjectBuffer.getBuffer().startswith(TestingFormatMagic)) {
1085-
// This is a special format used for testing.
1086-
auto ReaderOrErr =
1087-
loadTestingFormat(ObjectBuffer.getBuffer(), CompilationDir);
1088-
if (!ReaderOrErr)
1089-
return ReaderOrErr.takeError();
1090-
Readers.push_back(std::move(ReaderOrErr.get()));
1091-
return std::move(Readers);
1112+
if (ObjectBuffer.getBuffer().size() > sizeof(TestingFormatMagic)) {
1113+
uint64_t Magic =
1114+
support::endian::byte_swap<uint64_t, support::endianness::little>(
1115+
*reinterpret_cast<const uint64_t *>(ObjectBuffer.getBufferStart()));
1116+
if (Magic == TestingFormatMagic) {
1117+
// This is a special format used for testing.
1118+
auto ReaderOrErr =
1119+
loadTestingFormat(ObjectBuffer.getBuffer(), CompilationDir);
1120+
if (!ReaderOrErr)
1121+
return ReaderOrErr.takeError();
1122+
Readers.push_back(std::move(ReaderOrErr.get()));
1123+
return std::move(Readers);
1124+
}
10921125
}
10931126

10941127
auto BinOrErr = createBinary(ObjectBuffer);

llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,37 @@ void CoverageMappingWriter::write(raw_ostream &OS) {
249249
// Ensure that all file ids have at least one mapping region.
250250
assert(CurrentFileID == (VirtualFileMapping.size() - 1));
251251
}
252+
253+
void TestingFormatWriter::write(raw_ostream &OS, TestingFormatVersion Version) {
254+
auto ByteSwap = [](uint64_t N) {
255+
return support::endian::byte_swap<uint64_t, support::endianness::little>(N);
256+
};
257+
258+
// Output a 64bit magic number.
259+
auto Magic = ByteSwap(TestingFormatMagic);
260+
OS.write(reinterpret_cast<char *>(&Magic), sizeof(Magic));
261+
262+
// Output a 64bit version field.
263+
auto VersionLittle = ByteSwap(uint64_t(Version));
264+
OS.write(reinterpret_cast<char *>(&VersionLittle), sizeof(VersionLittle));
265+
266+
// Output the ProfileNames data.
267+
encodeULEB128(ProfileNamesData.size(), OS);
268+
encodeULEB128(ProfileNamesAddr, OS);
269+
OS << ProfileNamesData;
270+
271+
// Version2 adds an extra field to indicate the size of the
272+
// CoverageMappingData.
273+
if (Version == TestingFormatVersion::Version2)
274+
encodeULEB128(CoverageMappingData.size(), OS);
275+
276+
// Coverage mapping data is expected to have an alignment of 8.
277+
for (unsigned Pad = offsetToAlignment(OS.tell(), Align(8)); Pad; --Pad)
278+
OS.write(uint8_t(0));
279+
OS << CoverageMappingData;
280+
281+
// Coverage records data is expected to have an alignment of 8.
282+
for (unsigned Pad = offsetToAlignment(OS.tell(), Align(8)); Pad; --Pad)
283+
OS.write(uint8_t(0));
284+
OS << CoverageRecordsData;
285+
}

0 commit comments

Comments
 (0)