|  | // 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 |