blob: 924537e63a0f1695f835a53fca0201d2653bb9dd [file] [log] [blame] [edit]
/*
* Copyright (C) 2022, 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.
*/
#include "file.h"
#include <binder/Functional.h>
#include <binder/RecordedTransaction.h>
#include <binder/unique_fd.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <algorithm>
using namespace android::binder::impl;
using android::Parcel;
using android::binder::borrowed_fd;
using android::binder::ReadFully;
using android::binder::unique_fd;
using android::binder::WriteFully;
using android::binder::debug::RecordedTransaction;
#define PADDING8(s) ((8 - (s) % 8) % 8)
static_assert(PADDING8(0) == 0);
static_assert(PADDING8(1) == 7);
static_assert(PADDING8(7) == 1);
static_assert(PADDING8(8) == 0);
// Transactions are sequentially recorded to a file descriptor.
//
// An individual RecordedTransaction is written with the following format:
//
// WARNING: Though the following format is designed to be stable and
// extensible, it is under active development and should be considered
// unstable until this warning is removed.
//
// A RecordedTransaction is written to a file as a sequence of Chunks.
//
// A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum.
//
// The ChunkDescriptor identifies the type of Data in the chunk, and the size
// of the Data.
//
// The Data may be any uint32 number of bytes in length in [0-0xfffffff0].
//
// Padding is between [0-7] zero-bytes after the Data such that the Chunk ends
// on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the
// size of Padding.
//
// The checksum is a 64-bit wide XOR of all previous data from the start of the
// ChunkDescriptor to the end of Padding.
//
// ┌───────────────────────────┐
// │Chunk │
// │┌────────────────────────┐ │
// ││ChunkDescriptor │ │
// ││┌───────────┬──────────┐│ │
// │││chunkType │dataSize ├┼─┼─┐
// │││uint32_t │uint32_t ││ │ │
// ││└───────────┴──────────┘│ │ │
// │└────────────────────────┘ │ │
// │┌─────────────────────────┐│ │
// ││Data ││ │
// ││bytes * dataSize │◀─┘
// ││ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│
// ││ Padding ││
// │└───┴─────────────────────┘│
// │┌─────────────────────────┐│
// ││checksum ││
// ││uint64_t ││
// │└─────────────────────────┘│
// └───────────────────────────┘
//
// A RecordedTransaction is written as a Header Chunk with fields about the
// transaction, a Data Parcel chunk, a Reply Parcel Chunk, and an End Chunk.
// ┌──────────────────────┐
// │ Header Chunk │
// ├──────────────────────┤
// │ Sent Parcel Chunk │
// ├──────────────────────┤
// │ Reply Parcel Chunk │
// ├──────────────────────┤
// ║ End Chunk ║
// ╚══════════════════════╝
//
// On reading a RecordedTransaction, an unrecognized chunk is checksummed
// then skipped according to size information in the ChunkDescriptor. Chunks
// are read and either assimilated or skipped until an End Chunk is
// encountered. This has three notable implications:
//
// 1. Older and newer implementations should be able to read one another's
// Transactions, though there will be loss of information.
// 2. With the exception of the End Chunk, Chunks can appear in any order
// and even repeat, though this is not recommended.
// 3. If any Chunk is repeated, old values will be overwritten by versions
// encountered later in the file.
//
// No effort is made to ensure the expected chunks are present. A single
// End Chunk may therefore produce an empty, meaningless RecordedTransaction.
RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
mData = t.mData;
mSentDataOnly.setData(t.getDataParcel().data(), t.getDataParcel().dataSize());
mReplyDataOnly.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize());
}
std::optional<RecordedTransaction> RecordedTransaction::fromDetails(
const String16& interfaceName, uint32_t code, uint32_t flags, timespec timestamp,
const Parcel& dataParcel, const Parcel& replyParcel, status_t err) {
RecordedTransaction t;
t.mData.mHeader = {code,
flags,
static_cast<int32_t>(err),
dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0),
static_cast<int64_t>(timestamp.tv_sec),
static_cast<int32_t>(timestamp.tv_nsec),
0};
t.mData.mInterfaceName = std::string(String8(interfaceName).c_str());
if (interfaceName.size() != t.mData.mInterfaceName.size()) {
ALOGE("Interface Name is not valid. Contains characters that aren't single byte utf-8.");
return std::nullopt;
}
if (const auto* kernelFields = dataParcel.maybeKernelFields()) {
for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
uint64_t offset = kernelFields->mObjects[i];
t.mData.mSentObjectData.push_back(offset);
}
}
if (t.mSentDataOnly.setData(dataParcel.data(), dataParcel.dataBufferSize()) !=
android::NO_ERROR) {
ALOGE("Failed to set sent parcel data.");
return std::nullopt;
}
if (t.mReplyDataOnly.setData(replyParcel.data(), replyParcel.dataBufferSize()) !=
android::NO_ERROR) {
ALOGE("Failed to set reply parcel data.");
return std::nullopt;
}
return std::optional<RecordedTransaction>(std::move(t));
}
enum {
HEADER_CHUNK = 1,
DATA_PARCEL_CHUNK = 2,
REPLY_PARCEL_CHUNK = 3,
INTERFACE_NAME_CHUNK = 4,
DATA_PARCEL_OBJECT_CHUNK = 5,
END_CHUNK = 0x00ffffff,
};
struct ChunkDescriptor {
uint32_t chunkType = 0;
uint32_t dataSize = 0;
};
static_assert(sizeof(ChunkDescriptor) % 8 == 0);
constexpr uint32_t kMaxChunkDataSize = 0xfffffff0;
typedef uint64_t transaction_checksum_t;
std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) {
RecordedTransaction t;
ChunkDescriptor chunk;
const long pageSize = sysconf(_SC_PAGE_SIZE);
struct stat fileStat;
if (fstat(fd.get(), &fileStat) != 0) {
ALOGE("Unable to get file information");
return std::nullopt;
}
off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
if (fdCurrentPosition == -1) {
ALOGE("Invalid offset in file descriptor.");
return std::nullopt;
}
do {
if (fileStat.st_size < (fdCurrentPosition + (off_t)sizeof(ChunkDescriptor))) {
ALOGE("Not enough file remains to contain expected chunk descriptor");
return std::nullopt;
}
if (!ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) {
ALOGE("Failed to read ChunkDescriptor from fd %d. %s", fd.get(), strerror(errno));
return std::nullopt;
}
transaction_checksum_t checksum = *reinterpret_cast<transaction_checksum_t*>(&chunk);
fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
if (fdCurrentPosition == -1) {
ALOGE("Invalid offset in file descriptor.");
return std::nullopt;
}
off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
if (chunk.dataSize > kMaxChunkDataSize) {
ALOGE("Chunk data exceeds maximum size.");
return std::nullopt;
}
size_t chunkPayloadSize =
chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
if (chunkPayloadSize > (size_t)(fileStat.st_size - fdCurrentPosition)) {
ALOGE("Chunk payload exceeds remaining file size.");
return std::nullopt;
}
if (PADDING8(chunkPayloadSize) != 0) {
ALOGE("Invalid chunk size, not aligned %zu", chunkPayloadSize);
return std::nullopt;
}
size_t memoryMappedSize = chunkPayloadSize + mmapPayloadStartOffset;
void* mappedMemory = mmap(nullptr, memoryMappedSize, PROT_READ, MAP_SHARED, fd.get(),
mmapPageAlignedStart);
auto mmap_guard = make_scope_guard(
[mappedMemory, memoryMappedSize] { munmap(mappedMemory, memoryMappedSize); });
transaction_checksum_t* payloadMap =
reinterpret_cast<transaction_checksum_t*>(mappedMemory);
payloadMap += mmapPayloadStartOffset /
sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
// page-alignment
if (payloadMap == MAP_FAILED) {
ALOGE("Memory mapping failed for fd %d: %d %s", fd.get(), errno, strerror(errno));
return std::nullopt;
}
for (size_t checksumIndex = 0;
checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) {
checksum ^= payloadMap[checksumIndex];
}
if (checksum != 0) {
ALOGE("Checksum failed.");
return std::nullopt;
}
fdCurrentPosition = lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
if (fdCurrentPosition == -1) {
ALOGE("Invalid offset in file descriptor.");
return std::nullopt;
}
switch (chunk.chunkType) {
case HEADER_CHUNK: {
if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
ALOGE("Header Chunk indicated size %" PRIu32 "; Expected %zu.", chunk.dataSize,
sizeof(TransactionHeader));
return std::nullopt;
}
t.mData.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
break;
}
case INTERFACE_NAME_CHUNK: {
t.mData.mInterfaceName =
std::string(reinterpret_cast<char*>(payloadMap), chunk.dataSize);
break;
}
case DATA_PARCEL_CHUNK: {
if (t.mSentDataOnly.setData(reinterpret_cast<const unsigned char*>(payloadMap),
chunk.dataSize) != android::NO_ERROR) {
ALOGE("Failed to set sent parcel data.");
return std::nullopt;
}
break;
}
case REPLY_PARCEL_CHUNK: {
if (t.mReplyDataOnly.setData(reinterpret_cast<const unsigned char*>(payloadMap),
chunk.dataSize) != android::NO_ERROR) {
ALOGE("Failed to set reply parcel data.");
return std::nullopt;
}
break;
}
case DATA_PARCEL_OBJECT_CHUNK: {
const uint64_t* objects = reinterpret_cast<const uint64_t*>(payloadMap);
size_t metaDataSize = (chunk.dataSize / sizeof(uint64_t));
ALOGI("Total objects found in saved parcel %zu", metaDataSize);
for (size_t index = 0; index < metaDataSize; ++index) {
t.mData.mSentObjectData.push_back(objects[index]);
}
break;
}
case END_CHUNK:
break;
default:
ALOGI("Unrecognized chunk.");
break;
}
} while (chunk.chunkType != END_CHUNK);
return std::optional<RecordedTransaction>(std::move(t));
}
android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
size_t byteCount, const uint8_t* data) const {
if (byteCount > kMaxChunkDataSize) {
ALOGE("Chunk data exceeds maximum size");
return BAD_VALUE;
}
ChunkDescriptor descriptor = {.chunkType = chunkType,
.dataSize = static_cast<uint32_t>(byteCount)};
// Prepare Chunk content as byte *
const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor);
const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data);
// Add Chunk to intermediate buffer, except checksum
std::vector<std::byte> buffer;
buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor));
buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount);
std::byte zero{0};
buffer.insert(buffer.end(), PADDING8(byteCount), zero);
// Calculate checksum from buffer
transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data());
transaction_checksum_t checksumValue = 0;
for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) {
checksumValue ^= checksumData[idx];
}
// Write checksum to buffer
std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue);
buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
// Write buffer to file
if (!WriteFully(fd, buffer.data(), buffer.size())) {
ALOGE("Failed to write chunk fd %d", fd.get());
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const {
if (NO_ERROR !=
writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
reinterpret_cast<const uint8_t*>(&(mData.mHeader)))) {
ALOGE("Failed to write transactionHeader to fd %d", fd.get());
return UNKNOWN_ERROR;
}
if (NO_ERROR !=
writeChunk(fd, INTERFACE_NAME_CHUNK, mData.mInterfaceName.size() * sizeof(uint8_t),
reinterpret_cast<const uint8_t*>(mData.mInterfaceName.c_str()))) {
ALOGI("Failed to write Interface Name Chunk to fd %d", fd.get());
return UNKNOWN_ERROR;
}
if (NO_ERROR !=
writeChunk(fd, DATA_PARCEL_CHUNK, mSentDataOnly.dataBufferSize(), mSentDataOnly.data())) {
ALOGE("Failed to write sent Parcel to fd %d", fd.get());
return UNKNOWN_ERROR;
}
if (NO_ERROR !=
writeChunk(fd, REPLY_PARCEL_CHUNK, mReplyDataOnly.dataBufferSize(),
mReplyDataOnly.data())) {
ALOGE("Failed to write reply Parcel to fd %d", fd.get());
return UNKNOWN_ERROR;
}
if (NO_ERROR !=
writeChunk(fd, DATA_PARCEL_OBJECT_CHUNK, mData.mSentObjectData.size() * sizeof(uint64_t),
reinterpret_cast<const uint8_t*>(mData.mSentObjectData.data()))) {
ALOGE("Failed to write sent parcel object metadata to fd %d", fd.get());
return UNKNOWN_ERROR;
}
if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, nullptr)) {
ALOGE("Failed to write end chunk to fd %d", fd.get());
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
const std::string& RecordedTransaction::getInterfaceName() const {
return mData.mInterfaceName;
}
uint32_t RecordedTransaction::getCode() const {
return mData.mHeader.code;
}
uint32_t RecordedTransaction::getFlags() const {
return mData.mHeader.flags;
}
int32_t RecordedTransaction::getReturnedStatus() const {
return mData.mHeader.statusReturned;
}
timespec RecordedTransaction::getTimestamp() const {
time_t sec = mData.mHeader.timestampSeconds;
int32_t nsec = mData.mHeader.timestampNanoseconds;
return (timespec){.tv_sec = sec, .tv_nsec = nsec};
}
uint32_t RecordedTransaction::getVersion() const {
return mData.mHeader.version;
}
const std::vector<uint64_t>& RecordedTransaction::getObjectOffsets() const {
return mData.mSentObjectData;
}
const Parcel& RecordedTransaction::getDataParcel() const {
return mSentDataOnly;
}
const Parcel& RecordedTransaction::getReplyParcel() const {
return mReplyDataOnly;
}