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
This commit is contained in:
parent
5a611cb834
commit
e962930d19
6 changed files with 213 additions and 15 deletions
|
|
@ -42,4 +42,5 @@ cc_test {
|
|||
"libbase",
|
||||
"liblog",
|
||||
],
|
||||
require_root: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@
|
|||
|
||||
#define LOG_TAG "coverage"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/uio.h>
|
||||
#include <trusty/coverage/coverage.h>
|
||||
#include <trusty/coverage/record.h>
|
||||
#include <trusty/coverage/tipc.h>
|
||||
#include <trusty/tipc.h>
|
||||
|
||||
|
|
@ -137,12 +140,59 @@ Result<void> 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<std::pair<size_t, size_t>> 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<void> 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
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <trusty/coverage/coverage.h>
|
||||
#include <trusty/tipc.h>
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -35,13 +35,26 @@ class CoverageRecord {
|
|||
CoverageRecord(std::string tipc_dev, struct uuid* uuid);
|
||||
~CoverageRecord();
|
||||
Result<void> 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<void> SaveSancovFile(const std::string& filename);
|
||||
|
||||
private:
|
||||
Result<void> Rpc(coverage_client_req* req, int req_fd, coverage_client_resp* resp);
|
||||
|
||||
Result<std::pair<size_t, size_t>> GetRegionBounds(uint32_t region_type);
|
||||
|
||||
std::string tipc_dev_;
|
||||
unique_fd coverage_srv_fd_;
|
||||
struct uuid uuid_;
|
||||
|
|
|
|||
70
trusty/coverage/include/trusty/coverage/record.h
Normal file
70
trusty/coverage/include/trusty/coverage/record.h
Normal file
|
|
@ -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 <stdint.h>
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
|
@ -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++) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue