blob: ae8cb49ffde5e7b5f8f320d3cb3f29d9502feb45 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/fidlcat/lib/replay.h"
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "src/lib/fidl_codec/proto_value.h"
#include "src/lib/fidl_codec/semantic.h"
#include "tools/fidlcat/lib/event.h"
#include "tools/fidlcat/lib/syscall_decoder_dispatcher.h"
#include "tools/fidlcat/proto/session.pb.h"
namespace fidlcat {
std::shared_ptr<InvokedEvent> CreateInvoked(SyscallDisplayDispatcher* dispatcher,
uint64_t timestamp, uint64_t process_id,
uint64_t thread_id, Syscall* syscall) {
return std::make_shared<InvokedEvent>(
timestamp, dispatcher->CreateThread("foo", process_id, thread_id, nullptr), syscall);
}
Syscall* ReplayBuffer::GetSyscall(SyscallDisplayDispatcher* dispatcher) const {
switch (kind_) {
case Kind::kRead:
return dispatcher->SearchSyscall(etc_ ? "zx_channel_read_etc" : "zx_channel_read");
case Kind::kWrite:
return dispatcher->SearchSyscall(etc_ ? "zx_channel_write_etc" : "zx_channel_write");
case Kind::kCall:
return dispatcher->SearchSyscall(etc_ ? "zx_channel_call_etc" : "zx_channel_call");
}
}
void ReplayBuffer::AddWriteBytes(std::istream& stream) {
// Bytes are specified in hexadecimal (without any leading 0x).
// Up to 32 bytes can be specified on a line.
stream >> std::hex;
size_t count = write_byte_count_ - write_bytes_.size();
// The dump must have 32 bytes (unless there are less bytes remaining).
if (count > 32) {
count = 32;
}
while (count > 0) {
uint32_t data;
stream >> data;
write_bytes_.emplace_back(data);
--count;
}
stream >> std::dec;
}
void ReplayBuffer::AddWriteHandles(std::istream& stream) {
// Handles are specified in hexadecimal (without any leading 0x).
// Up to 8 handles can be specified on a line.
stream >> std::hex;
size_t count = write_handle_count_ - write_handles_.size();
// The dump must have 8 handles (unless there are less handles remaining).
if (count > 8) {
count = 8;
}
zx_handle_disposition_t handle = {.operation = fidl_codec::kNoHandleDisposition,
.handle = 0,
.type = ZX_OBJ_TYPE_NONE,
.rights = 0,
.result = ZX_OK};
while (count > 0) {
stream >> handle.handle;
write_handles_.emplace_back(handle);
--count;
}
stream >> std::dec;
}
void ReplayBuffer::AddWriteEtcHandle(std::istream& stream) {
// Only one handle disposition is specified per line. The fields are:
// - operation (0 or 1).
// - handle (in hexdecimal wiout any leading 0x).
// - rights (in hexdecimal wiout any leading 0x).
// - type (in decimal).
zx_handle_disposition_t handle = {.operation = fidl_codec::kNoHandleDisposition,
.handle = 0,
.type = ZX_OBJ_TYPE_NONE,
.rights = 0,
.result = ZX_OK};
stream >> handle.operation >> std::hex >> handle.handle >> handle.rights >> std::dec >>
handle.type >> handle.result;
write_handles_.emplace_back(handle);
}
void ReplayBuffer::AddReadBytes(std::istream& stream) {
// Bytes are specified in hexadecimal (without any leading 0x).
// Up to 32 bytes can be specified on a line.
stream >> std::hex;
size_t count = read_byte_count_ - read_bytes_.size();
// The dump must have 32 bytes (unless there are less bytes remaining).
if (count > 32) {
count = 32;
}
while (count > 0) {
uint32_t data;
stream >> data;
read_bytes_.emplace_back(data);
--count;
}
stream >> std::dec;
}
void ReplayBuffer::AddReadHandles(std::istream& stream) {
// Handles are specified in hexadecimal (without any leading 0x).
// Up to 8 handles can be specified on a line.
stream >> std::hex;
size_t count = read_handle_count_ - read_handles_.size();
// The dump must have 8 handles (unless there are less handles remaining).
if (count > 8) {
count = 8;
}
zx_handle_disposition_t handle = {.operation = fidl_codec::kNoHandleDisposition,
.handle = 0,
.type = ZX_OBJ_TYPE_NONE,
.rights = 0,
.result = ZX_OK};
while (count > 0) {
stream >> handle.handle;
read_handles_.emplace_back(handle);
--count;
}
stream >> std::dec;
}
void ReplayBuffer::Dispatch(SyscallDisplayDispatcher* dispatcher) {
// Gets the definition of the syscall (from kind_ and etc_).
Syscall* syscall = GetSyscall(dispatcher);
// Creates the invoked event.
std::shared_ptr<InvokedEvent> invoked_event =
CreateInvoked(dispatcher, invoked_timestamp_, process_id_, thread_id_, syscall);
// Sets the inline fields shared by all the channel syscalls.
zx_handle_disposition_t handle;
handle.operation = fidl_codec::kNoHandleDisposition;
handle.handle = channel_;
handle.rights = 0;
handle.type = ZX_OBJ_TYPE_NONE;
handle.result = ZX_OK;
invoked_event->AddInlineField(syscall->SearchInlineMember("handle", /*invoked=*/true),
std::make_unique<fidl_codec::HandleValue>(handle));
invoked_event->AddInlineField(
syscall->SearchInlineMember("options", /*invoked=*/true),
std::make_unique<fidl_codec::IntegerValue>(/*absolute_value=*/0, /*negative=*/false));
if ((kind_ == Kind::kWrite) || kind_ == Kind::kCall) {
// Decodes the outgoing message.
fidl_codec::DecodedMessage message;
std::stringstream error_stream;
message.DecodeMessage(dispatcher->MessageDecoderDispatcher(), process_id_, channel_,
write_bytes_.data(), write_byte_count_, write_handles_.data(),
write_handle_count_,
(kind_ == Kind::kCall) ? fidl_codec::SyscallFidlType::kOutputRequest
: fidl_codec::SyscallFidlType::kOutputMessage,
error_stream);
invoked_event->AddOutlineField(
syscall->SearchOutlineMember("", /*invoked=*/true),
std::make_unique<fidl_codec::FidlMessageValue>(&message, error_stream.str(),
write_bytes_.data(), write_byte_count_,
write_handles_.data(), write_handle_count_));
}
dispatcher->AddInvokedEvent(invoked_event);
// Creates the output event.
auto output_event = std::make_shared<OutputEvent>(output_timestamp_, invoked_event->thread(),
syscall, status_, invoked_event);
if (((kind_ == Kind::kRead) || (kind_ == Kind::kCall)) && (status_ == ZX_OK)) {
// Decodes the incoming message.
fidl_codec::DecodedMessage message;
std::stringstream error_stream;
message.DecodeMessage(dispatcher->MessageDecoderDispatcher(), process_id_, channel_,
read_bytes_.data(), read_byte_count_, read_handles_.data(),
read_handle_count_,
(kind_ == Kind::kCall) ? fidl_codec::SyscallFidlType::kInputResponse
: fidl_codec::SyscallFidlType::kInputMessage,
error_stream);
output_event->AddOutlineField(syscall->SearchOutlineMember("", /*invoked=*/false),
std::make_unique<fidl_codec::FidlMessageValue>(
&message, error_stream.str(), read_bytes_.data(),
read_byte_count_, read_handles_.data(), read_handle_count_));
}
dispatcher->AddOutputEvent(std::move(output_event));
}
bool Replay::DumpProto(const std::string& proto_file_name) {
if (proto_file_name == "-") {
return DumpProto(std::cin);
}
std::fstream input(proto_file_name, std::ios::in | std::ios::binary);
if (input.fail()) {
FX_LOGS(ERROR) << "Can't open <" << proto_file_name << "> for reading.";
return false;
}
if (!DumpProto(input)) {
FX_LOGS(ERROR) << "Failed to parse session from file <" << proto_file_name << ">.";
return false;
}
return true;
}
bool Replay::DumpProto(std::istream& is) {
proto::Session session;
if (!session.ParseFromIstream(&is)) {
return false;
}
std::cout << session.DebugString();
return true;
}
bool Replay::ReplayProto(const std::string& proto_file_name) {
if (proto_file_name == "-") {
return ReplayProto("standard input", std::cin);
}
std::fstream input(proto_file_name, std::ios::in | std::ios::binary);
if (input.fail()) {
FX_LOGS(ERROR) << "Can't open <" << proto_file_name << "> for reading.";
return false;
}
return ReplayProto("file <" + proto_file_name + ">", input);
}
bool Replay::ReplayProto(const std::string& file_name, std::istream& is) {
proto::Session session;
if (!session.ParseFromIstream(&is)) {
FX_LOGS(ERROR) << "Failed to parse session from " << file_name << ".";
return false;
}
bool ok = true;
for (int index = 0; index < session.process_size(); ++index) {
const proto::Process& process = session.process(index);
if (dispatcher()->SearchProcess(process.koid()) != nullptr) {
FX_LOGS(INFO) << "Error reading protobuf " << file_name << ": process " << process.name()
<< " koid=" << process.koid() << " defined multiple times.";
ok = false;
} else {
dispatcher()->CreateProcess(process.name(), process.koid(), nullptr);
for (int handle_index = 0; handle_index < process.linked_handles_size(); ++handle_index) {
const proto::LinkedHandles& linked_handles = process.linked_handles(handle_index);
dispatcher()->inference().AddLinkedHandles(process.koid(), linked_handles.handle_0(),
linked_handles.handle_1());
}
}
}
for (int index = 0; index < session.thread_size(); ++index) {
const proto::Thread& thread = session.thread(index);
if (dispatcher()->SearchThread(thread.koid()) != nullptr) {
FX_LOGS(INFO) << "Error reading protobuf " << file_name << ": thread " << thread.koid()
<< " defined multiple times.";
ok = false;
} else {
Process* process = dispatcher()->SearchProcess(thread.process_koid());
if (process == nullptr) {
FX_LOGS(ERROR) << "Error reading protobuf " << file_name << ": process "
<< thread.process_koid() << " not found for thread " << thread.koid() << '.';
ok = false;
}
dispatcher()->CreateThread(thread.koid(), process);
}
}
for (int index = 0; index < session.handle_description_size(); ++index) {
const proto::HandleDescription& proto_handle_description = session.handle_description(index);
Thread* thread = dispatcher()->SearchThread(proto_handle_description.thread_koid());
if (thread == nullptr) {
FX_LOGS(ERROR) << "Error reading protobuf file " << file_name << ": thread "
<< proto_handle_description.thread_koid() << " not found for handle.";
ok = false;
} else {
HandleInfo* handle_info = dispatcher()->CreateHandleInfo(
thread, proto_handle_description.handle(), proto_handle_description.creation_time(),
proto_handle_description.startup());
handle_info->set_object_type(proto_handle_description.object_type());
handle_info->set_koid(proto_handle_description.koid());
dispatcher()->inference().AddKoidHandleInfo(proto_handle_description.koid(), handle_info);
}
auto inferred_handle_info = std::make_unique<fidl_codec::semantic::InferredHandleInfo>(
proto_handle_description.type(), proto_handle_description.fd(),
proto_handle_description.path(), proto_handle_description.attributes());
dispatcher()->inference().AddInferredHandleInfo(thread->process()->koid(),
proto_handle_description.handle(),
std::move(inferred_handle_info));
}
for (int index = 0; index < session.linked_koids_size(); ++index) {
const proto::LinkedKoids& linked_koids = session.linked_koids(index);
dispatcher()->inference().AddLinkedKoids(linked_koids.koid_0(), linked_koids.koid_1());
}
for (int index = 0; index < session.event_size(); ++index) {
const proto::Event& proto_event = session.event(index);
if (!DecodeAndDispatchEvent(proto_event)) {
ok = false;
}
}
return ok;
}
void Replay::DecodeTrace(std::istream& is) {
// Decodes a trace stream, line per line, until the end of the stream.
while (!is.eof()) {
DecodeTraceLine(is);
}
}
void Replay::DecodeTraceLine(std::istream& is) {
// Decodes one trace line.
std::string line;
std::getline(is, line);
auto position = line.find("syscall ");
if (position == std::string::npos) {
// If the line doesn't include the keyword syscall, it's a standard trace line. In that case
// the line is output without modification (pass through).
dispatcher()->os() << line << '\n';
} else {
std::stringstream stream(line);
stream.seekg(position + 8, stream.cur);
// Format for all decoded traces:
// syscall |instance_id| |action| ...
uintptr_t instance;
std::string action;
stream >> std::hex >> instance >> std::dec >> action;
if (action == "process") {
// Defines the name of a process. The format is:
// syscall |instance_id| process |process_id| |process_name|
// For example:
// syscall 0x7ffd6863e9c0 process 2916209 FfxDoctor
if (position > 0) {
// Pass through any text before the keyword "syscall".
dispatcher()->os() << line.substr(0, position) << '\n';
}
std::string process_name;
uint64_t process_id;
stream >> process_id >> process_name;
Process* process = dispatcher()->SearchProcess(process_id);
if (process == nullptr) {
process = dispatcher()->CreateProcess(process_name, process_id, nullptr);
}
return;
}
if (action == "startup") {
// Defines a startup handle. That is a handle which is available to the user code either
// because the handle was given to the process (Fuchsia case) or because the handle has a
// special handling (Linux and other OS case).
// The format is (all fields on one line):
// syscall |instance_id| startup |process_id| |thread_id| |handle_type|(|handle|)
// |type| |path|
// For example:
// syscall 0x7ffd68636f90 startup 2916209 2916210 Channel(1) dir /svc
if (position > 0) {
// Pass through any text before the keyword "syscall".
dispatcher()->os() << line.substr(0, position) << '\n';
}
uint64_t process_id;
uint64_t thread_id;
stream >> process_id >> thread_id >> std::ws;
char handle_type[100];
stream.get(handle_type, 100, '(');
stream.seekg(1, stream.cur);
uint32_t handle;
stream >> handle;
stream.seekg(1, stream.cur);
std::string type;
std::string path;
stream >> type >> path;
Process* process = dispatcher()->SearchProcess(process_id);
if (process != nullptr) {
HandleInfo* handle_info = process->SearchHandleInfo(handle);
if (handle_info != nullptr) {
handle_info->set_startup();
}
}
dispatcher()->inference().AddInferredHandleInfo(process_id, handle, type, path, "");
return;
}
if (action == "channel_create") {
// Defines a call to zx_channel_create. The format is (on one line):
// syscall |instance_id| channel_create |timestamp| |process_id| |thread_id| |out0| |out1|
// |status|
// The fields out0 and out1 are in hexadecimal without a leading 0x.
// For example:
// syscall 0x7ffd68637fd0 channel_create 1234 2916209 2916210 9 a 0
if (position > 0) {
// Pass through any text before the keyword "syscall".
dispatcher()->os() << line.substr(0, position) << '\n';
}
uint64_t timestamp;
uint64_t process_id;
uint64_t thread_id;
uint32_t out0;
uint32_t out1;
zx_status_t status;
stream >> timestamp >> process_id >> thread_id >> std::hex >> out0 >> out1 >> std::dec >>
status;
Thread* thread = dispatcher()->CreateThread("foo", process_id, thread_id, nullptr);
// Specifies that both handles are channels.
dispatcher()
->CreateHandleInfo(thread, out0, 0, /*startup=*/false)
->set_object_type(ZX_OBJ_TYPE_CHANNEL);
dispatcher()
->CreateHandleInfo(thread, out1, 0, /*startup=*/false)
->set_object_type(ZX_OBJ_TYPE_CHANNEL);
// Specifies that the two channels are linked.
dispatcher()->inference().AddLinkedHandles(process_id, out0, out1);
dispatcher()->inference().AddLinkedHandles(process_id, out1, out0);
// Creates and adds the invoked and the output events.
Syscall* syscall = dispatcher()->SearchSyscall("zx_channel_create");
std::shared_ptr<InvokedEvent> invoked_event =
CreateInvoked(dispatcher(), timestamp, process_id, thread_id, syscall);
dispatcher()->AddInvokedEvent(invoked_event);
auto output_event = std::make_shared<OutputEvent>(timestamp, invoked_event->thread(), syscall,
status, invoked_event);
zx_handle_disposition_t handle;
handle.operation = fidl_codec::kNoHandleDisposition;
handle.handle = out0;
handle.rights = 0;
handle.type = ZX_OBJ_TYPE_NONE;
handle.result = ZX_OK;
output_event->AddInlineField(syscall->SearchInlineMember("out0", /*invoked=*/false),
std::make_unique<fidl_codec::HandleValue>(handle));
handle.handle = out1;
output_event->AddInlineField(syscall->SearchInlineMember("out1", /*invoked=*/false),
std::make_unique<fidl_codec::HandleValue>(handle));
dispatcher()->AddOutputEvent(std::move(output_event));
return;
}
if ((action == "channel_call") || (action == "channel_call_etc")) {
// Defines a zx_channel_call or a zx_channel_call_etc syscall. The format is (on one line):
// syscall |instance_id| channel_call |timestamp| |process_id| |thread_id| |channel| |bytes|
// |handles|
// The field channel is in hexdecimal without a leading 0x.
// The field bytes specifies the number of bytes to be written.
// The field handles specifies the number of handles to be written.
// If bytes or handles are not zero, this line will be followed by one or several lines which
// define the bytes and handles. Each of these lines will have the same instance_id.
// For example:
// syscall 0x5591382ba060 channel_call 1234 2916209 2916210 a 96 0
if (position > 0) {
// Pass through any text before the keyword "syscall".
dispatcher()->os() << line.substr(0, position) << '\n';
}
uint64_t timestamp;
uint64_t process_id;
uint64_t thread_id;
uint32_t channel;
uint32_t write_byte_count;
uint32_t write_handle_count;
stream >> timestamp >> process_id >> thread_id >> std::hex >> channel >> std::dec >>
write_byte_count >> write_handle_count;
// Creates a ReplayBuffer used to keep the context while the bytes and handles are read.
auto buffer = std::make_unique<ReplayBuffer>(timestamp, process_id, thread_id,
ReplayBuffer::Kind::kCall,
/*etc=*/action == "channel_call_etc", channel);
buffer->SetWrite(write_byte_count, write_handle_count);
buffers_[instance] = std::move(buffer);
return;
}
if ((action == "channel_write") || (action == "channel_write_etc")) {
// Defines a zx_channel_write or a zx_channel_write_etc syscall. The format is (on one line):
// syscall |instance_id| channel_write |timestamp| |process_id| |thread_id| |channel| |bytes|
// |handles|
// The field channel is in hexdecimal without a leading 0x.
// The field bytes specifies the number of bytes to be written.
// The field handles specifies the number of handles to be written.
// If bytes or handles are not zero, this line will be followed by one or several lines which
// define the bytes and handles. Each of these lines will have the same instance_id.
// For example:
// syscall 0x5591382ba060 channel_write_etc 1234 2916209 2916210 a 96 0
if (position > 0) {
// Pass through any text before the keyword "syscall".
dispatcher()->os() << line.substr(0, position) << '\n';
}
uint64_t timestamp;
uint64_t process_id;
uint64_t thread_id;
uint32_t channel;
uint32_t write_byte_count;
uint32_t write_handle_count;
stream >> timestamp >> process_id >> thread_id >> std::hex >> channel >> std::dec >>
write_byte_count >> write_handle_count;
// Creates a ReplayBuffer used to keep the context while the bytes and handles are read.
auto buffer = std::make_unique<ReplayBuffer>(timestamp, process_id, thread_id,
ReplayBuffer::Kind::kWrite,
/*etc=*/action == "channel_write_etc", channel);
buffer->SetWrite(write_byte_count, write_handle_count);
buffers_[instance] = std::move(buffer);
return;
}
if ((action == "channel_read") || (action == "channel_read_etc")) {
// Defines a zx_channel_read or a zx_channel_read_etc syscall. The format is (on one line):
// syscall |instance_id| channel_read |timestamp| |process_id| |thread_id| |channel| |status|
// |bytes| |handles|
// The field channel is in hexdecimal without a leading 0x.
// The field bytes specifies the number of bytes to be written.
// The field handles specifies the number of handles to be written.
// If bytes or handles are not zero, this line will be followed by one or several lines which
// define the bytes and handles. Each of these lines will have the same instance_id.
// For example:
// syscall 0x7ffd686381a0 channel_read 1234 2916209 2916209 9 0 96 0
if (position > 0) {
// Pass through any text before the keyword "syscall".
dispatcher()->os() << line.substr(0, position) << '\n';
}
uint64_t timestamp;
uint64_t process_id;
uint64_t thread_id;
uint32_t channel;
zx_status_t status;
uint32_t read_byte_count;
uint32_t read_handle_count;
stream >> timestamp >> process_id >> thread_id >> std::hex >> channel >> std::dec >> status >>
read_byte_count >> read_handle_count;
// Creates a ReplayBuffer used to keep the context while the bytes and handles are read.
auto buffer = std::make_unique<ReplayBuffer>(timestamp, process_id, thread_id,
ReplayBuffer::Kind::kRead,
/*etc=*/action == "channel_read_etc", channel);
buffer->SetRead(read_byte_count, read_handle_count);
buffer->SetStatus(timestamp, status);
if (buffer->DecodeOk()) {
// Case for which there is no bytes or handles. This happends when the status is not ZX_OK.
buffer->Dispatch(dispatcher());
} else {
buffers_[instance] = std::move(buffer);
}
return;
}
// The line is not a header line. Search for a pending buffer with the instance id.
auto buffer = SearchBuffer(instance);
if (buffer == nullptr) {
// No buffer found. The line is passed through.
dispatcher()->os() << line << '\n';
return;
}
// Checks for possible actions on a buffer.
if (action == "call_status") {
// Defines the status for a zx_channel_call. The format is:
// syscall |instance_id| call_status |timestamp| |status| |bytes| |channels|
// For example:
// syscall 0x559138249750 call_status 1234 0 48 0
// If bytes or handles are not zero, this line will be followed by one or several lines which
// define the bytes and handles. Each of these lines will have the same instance_id.
uint64_t timestamp;
zx_status_t status;
uint32_t read_byte_count;
uint32_t read_handle_count;
stream >> timestamp >> status >> read_byte_count >> read_handle_count;
buffer->SetStatus(timestamp, status);
buffer->SetRead(read_byte_count, read_handle_count);
} else if (action == "write_status") {
// Defines the status for a zx_channel_write. The format is:
// syscall |instance_id| write_status |timestamp| |status|
// For example:
// syscall 0x559138249750 write_status 1234 0
uint64_t timestamp;
zx_status_t status;
stream >> timestamp >> status;
buffer->SetStatus(timestamp, status);
} else if (action == "write_bytes") {
buffer->AddWriteBytes(stream);
} else if (action == "write_handles") {
buffer->AddWriteHandles(stream);
} else if (action == "write_etc_handle") {
buffer->AddWriteEtcHandle(stream);
} else if (action == "read_bytes") {
buffer->AddReadBytes(stream);
} else if (action == "read_handles") {
buffer->AddReadHandles(stream);
} else {
// No valid action found. The line is passed through.
dispatcher()->os() << line << '\n';
return;
}
if (position > 0) {
// Pass through any text before the keyword "syscall".
dispatcher()->os() << line.substr(0, position) << '\n';
}
// If the buffer is fully decoded, dispatches it and destroys it.
if (buffer->DecodeOk()) {
buffer->Dispatch(dispatcher());
buffers_.erase(instance);
}
}
}
} // namespace fidlcat