From e962930d19efd198920df2f77b95f78f3b394751 Mon Sep 17 00:00:00 2001 From: Stephen Crane Date: Mon, 16 Nov 2020 18:00:53 -0800 Subject: [PATCH] trusty: Retrieve coverage PCs from coverage record Adds the ability to retrieve and save program counter information from the trusty coverage record data. PC information is saved to a .sancov file, parseable by the LLVM sancov tool. Sancov can then symbolize and display this coverage information for consumption by humans. Adds a sancov dump to the libtrusty_coverage_test for testing. Bug: 175221942 Test: atest libtrusty_coverage_test Test: Retrieve sancov file and manually symbolize with sancov Change-Id: I342ea2ca9abb87986b2904ff69415544ee6070fc --- trusty/coverage/Android.bp | 1 + trusty/coverage/coverage.cpp | 115 +++++++++++++++++- trusty/coverage/coverage_test.cpp | 15 ++- .../include/trusty/coverage/coverage.h | 17 ++- .../coverage/include/trusty/coverage/record.h | 70 +++++++++++ trusty/fuzz/counters.cpp | 10 +- 6 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 trusty/coverage/include/trusty/coverage/record.h diff --git a/trusty/coverage/Android.bp b/trusty/coverage/Android.bp index a4adf81be..3d2caa6cc 100644 --- a/trusty/coverage/Android.bp +++ b/trusty/coverage/Android.bp @@ -42,4 +42,5 @@ cc_test { "libbase", "liblog", ], + require_root: true, } diff --git a/trusty/coverage/coverage.cpp b/trusty/coverage/coverage.cpp index 1162f42aa..9413ce385 100644 --- a/trusty/coverage/coverage.cpp +++ b/trusty/coverage/coverage.cpp @@ -16,12 +16,15 @@ #define LOG_TAG "coverage" +#include #include #include #include +#include #include #include #include +#include #include #include @@ -137,12 +140,59 @@ Result CoverageRecord::Open() { return {}; } -void CoverageRecord::Reset() { - for (size_t i = 0; i < shm_len_; i++) { +void CoverageRecord::ResetFullRecord() { + auto header_region = GetRegionBounds(COV_START); + if (!header_region) { + // If the header cannot be parsed, we can't reset the proper region yet. + return; + } + + for (size_t i = header_region->second; i < shm_len_; i++) { *((volatile uint8_t*)shm_ + i) = 0; } } +void CoverageRecord::ResetCounts() { + volatile uint8_t* begin = nullptr; + volatile uint8_t* end = nullptr; + GetRawCounts(&begin, &end); + + for (volatile uint8_t* x = begin; x < end; x++) { + *x = 0; + } +} + +void CoverageRecord::ResetPCs() { + volatile uintptr_t* begin = nullptr; + volatile uintptr_t* end = nullptr; + GetRawPCs(&begin, &end); + + for (volatile uintptr_t* x = begin; x < end; x++) { + *x = 0; + } +} + +Result> CoverageRecord::GetRegionBounds(uint32_t region_type) { + assert(shm_); + + auto header = (volatile struct coverage_record_header*)shm_; + + if (header->type != COV_START) { + return Error() << "Header not yet valid"; + } + + for (++header; header->type != COV_TOTAL_LENGTH; ++header) { + if (header->type == region_type) { + // Coverage record must end with a COV_TOTAL_LENGTH header entry, so + // it is always safe to read the next entry since we don't iterate + // over the COV_TOTAL_LENGTH entry. + return {{header->offset, (header + 1)->offset}}; + } + } + + return Error() << "Could not find coverage region type: " << region_type; +} + void CoverageRecord::GetRawData(volatile void** begin, volatile void** end) { assert(shm_); @@ -150,7 +200,35 @@ void CoverageRecord::GetRawData(volatile void** begin, volatile void** end) { *end = (uint8_t*)(*begin) + record_len_; } -uint64_t CoverageRecord::CountEdges() { +void CoverageRecord::GetRawCounts(volatile uint8_t** begin, volatile uint8_t** end) { + auto region = GetRegionBounds(COV_8BIT_COUNTERS); + if (!region) { + *begin = 0; + *end = 0; + return; + } + + assert(region->second <= record_len_); + + *begin = (volatile uint8_t*)shm_ + region->first; + *end = (volatile uint8_t*)shm_ + region->second; +} + +void CoverageRecord::GetRawPCs(volatile uintptr_t** begin, volatile uintptr_t** end) { + auto region = GetRegionBounds(COV_INSTR_PCS); + if (!region) { + *begin = 0; + *end = 0; + return; + } + + assert(region->second <= record_len_); + + *begin = (volatile uintptr_t*)((volatile uint8_t*)shm_ + region->first); + *end = (volatile uintptr_t*)((volatile uint8_t*)shm_ + region->second); +} + +uint64_t CoverageRecord::TotalEdgeCounts() { assert(shm_); uint64_t counter = 0; @@ -158,7 +236,7 @@ uint64_t CoverageRecord::CountEdges() { volatile uint8_t* begin = NULL; volatile uint8_t* end = NULL; - GetRawData((volatile void**)&begin, (volatile void**)&end); + GetRawCounts(&begin, &end); for (volatile uint8_t* x = begin; x < end; x++) { counter += *x; @@ -167,6 +245,35 @@ uint64_t CoverageRecord::CountEdges() { return counter; } +Result CoverageRecord::SaveSancovFile(const std::string& filename) { + android::base::unique_fd output_fd(TEMP_FAILURE_RETRY(creat(filename.c_str(), 00644))); + if (!output_fd.ok()) { + return ErrnoError() << "Could not open sancov file"; + } + + uint64_t magic; + if (sizeof(uintptr_t) == 8) { + magic = 0xC0BFFFFFFFFFFF64; + } else if (sizeof(uintptr_t) == 4) { + magic = 0xC0BFFFFFFFFFFF32; + } + WriteFully(output_fd, &magic, sizeof(magic)); + + volatile uintptr_t* begin = nullptr; + volatile uintptr_t* end = nullptr; + + GetRawPCs(&begin, &end); + + for (volatile uintptr_t* pc_ptr = begin; pc_ptr < end; pc_ptr++) { + uintptr_t pc = *pc_ptr; + if (pc) { + WriteFully(output_fd, &pc, sizeof(pc)); + } + } + + return {}; +} + } // namespace coverage } // namespace trusty } // namespace android diff --git a/trusty/coverage/coverage_test.cpp b/trusty/coverage/coverage_test.cpp index d8df7a46f..c1efca63b 100644 --- a/trusty/coverage/coverage_test.cpp +++ b/trusty/coverage/coverage_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -27,6 +28,7 @@ using std::unique_ptr; #define TIPC_DEV "/dev/trusty-ipc-dev0" #define TEST_SRV_PORT "com.android.trusty.sancov.test.srv" +#define TEST_SRV_MODULE "srv.syms.elf" namespace android { namespace trusty { @@ -54,8 +56,8 @@ class CoverageTest : public ::testing::Test { }; TEST_F(CoverageTest, CoverageReset) { - record_->Reset(); - auto counter = record_->CountEdges(); + record_->ResetFullRecord(); + auto counter = record_->TotalEdgeCounts(); ASSERT_EQ(counter, 0); } @@ -69,7 +71,7 @@ TEST_F(CoverageTest, TestServerCoverage) { for (size_t i = 1; i < sizeof(magic) * 8; i++) { /* Reset coverage */ - record_->Reset(); + record_->ResetCounts(); /* Send message to test server */ uint32_t msg = magic & ~(mask << i); @@ -81,10 +83,15 @@ TEST_F(CoverageTest, TestServerCoverage) { ASSERT_EQ(rc, sizeof(msg)); /* Count number of non-unique blocks executed */ - auto counter = record_->CountEdges(); + auto counter = record_->TotalEdgeCounts(); /* Each consecutive input should exercise more or same blocks */ ASSERT_GE(counter, high_watermark); high_watermark = counter; + + auto sancov_filename = android::base::StringPrintf( + "/data/local/tmp/" TEST_SRV_MODULE ".%d.sancov", getpid()); + auto res = record_->SaveSancovFile(sancov_filename); + ASSERT_TRUE(res.ok()); } ASSERT_GT(high_watermark, 0); diff --git a/trusty/coverage/include/trusty/coverage/coverage.h b/trusty/coverage/include/trusty/coverage/coverage.h index b61b95930..b6d46eb1a 100644 --- a/trusty/coverage/include/trusty/coverage/coverage.h +++ b/trusty/coverage/include/trusty/coverage/coverage.h @@ -35,13 +35,26 @@ class CoverageRecord { CoverageRecord(std::string tipc_dev, struct uuid* uuid); ~CoverageRecord(); Result Open(); - void Reset(); + void ResetFullRecord(); + void ResetCounts(); + void ResetPCs(); void GetRawData(volatile void** begin, volatile void** end); - uint64_t CountEdges(); + void GetRawCounts(volatile uint8_t** begin, volatile uint8_t** end); + void GetRawPCs(volatile uintptr_t** begin, volatile uintptr_t** end); + uint64_t TotalEdgeCounts(); + + /** + * Save the current set of observed PCs to the given filename. + * The resulting .sancov file can be parsed via the LLVM sancov tool to see + * coverage statistics and visualize coverage. + */ + Result SaveSancovFile(const std::string& filename); private: Result Rpc(coverage_client_req* req, int req_fd, coverage_client_resp* resp); + Result> GetRegionBounds(uint32_t region_type); + std::string tipc_dev_; unique_fd coverage_srv_fd_; struct uuid uuid_; diff --git a/trusty/coverage/include/trusty/coverage/record.h b/trusty/coverage/include/trusty/coverage/record.h new file mode 100644 index 000000000..bfe06f335 --- /dev/null +++ b/trusty/coverage/include/trusty/coverage/record.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This file needs to be kept in-sync with its counterpart on Trusty side: + * trusty/user/base/lib/coverage/common/include/lib/coverage/common/record.h */ + +#pragma once + +#include + +/** + * enum coverage_record_type - Coverage region header type + * @COV_START: Magic header start marker + * @COV_8BIT_COUNTERS: 8bit counter for each instrumentation point + * @COV_INSTR_PCS: Pointer length offset of each instrumentation point from the + * start of the binary + * @COV_TOTAL_LENGTH: Total length of the entire coverage record, must be the + * last header item. + * + * Describes the type of a region of the coverage record. See &struct + * coverage_record_header. + */ +enum coverage_record_type { + COV_START = 0x434f5652, + COV_8BIT_COUNTERS = 1, + COV_INSTR_PCS = 2, + COV_TOTAL_LENGTH = 0, +}; + +/** + * struct coverage_record_header - Header entry describing a region of the + * coverage record. + * @type: type of the region, must be one of @enum coverage_record_type + * @offset: offset from the beginning of the header to the start of the region + * + * Coverage records start with a header which is a list of struct + * coverage_record_header, beginning with an entry with type COV_START and + * terminated with an entry with type COV_TOTAL_LENGTH. Each of these header + * entries corresponds to a region of the record, with the offset indicating the + * offset of the start of that region from the beginning of the record (i.e. the + * beginning of the header). Each record type and offset is 32-bit field with + * native endianness. The first header item must be COV_START with a 0 offset. + * The COV_START entry should be initialized when the coverage header is + * complete and ready for consumption by the client, because coverage record + * initialization happens asynchronously. The final header item, + * COV_TOTAL_LENGTH, which must always be present, indicates the total length of + * the coverage record, including the header. + * + * Coverage regions should be contiguous, so the end of one region is the start + * of the next, and the coverage header must be in the same order as the regions + * in the record body. Thus we can compute the length of a region by subtracting + * the region's offset from the offset of the next header item. + */ +struct coverage_record_header { + uint32_t type; + uint32_t offset; +}; diff --git a/trusty/fuzz/counters.cpp b/trusty/fuzz/counters.cpp index 3fc9f48e7..8c79475c3 100644 --- a/trusty/fuzz/counters.cpp +++ b/trusty/fuzz/counters.cpp @@ -42,9 +42,9 @@ ExtraCounters::ExtraCounters(coverage::CoverageRecord* record) : record_(record) assert(fuzzer::ExtraCountersBegin()); assert(fuzzer::ExtraCountersEnd()); - uint8_t* begin = NULL; - uint8_t* end = NULL; - record_->GetRawData((volatile void**)&begin, (volatile void**)&end); + volatile uint8_t* begin = NULL; + volatile uint8_t* end = NULL; + record_->GetRawCounts(&begin, &end); assert(end - begin <= sizeof(counters)); } @@ -53,7 +53,7 @@ ExtraCounters::~ExtraCounters() { } void ExtraCounters::Reset() { - record_->Reset(); + record_->ResetCounts(); fuzzer::ClearExtraCounters(); } @@ -61,7 +61,7 @@ void ExtraCounters::Flush() { volatile uint8_t* begin = NULL; volatile uint8_t* end = NULL; - record_->GetRawData((volatile void**)&begin, (volatile void**)&end); + record_->GetRawCounts(&begin, &end); size_t num_counters = end - begin; for (size_t i = 0; i < num_counters; i++) {