blob: 87dc43d6052f51dd80fda7f72910166016d3f2c4 [file] [log] [blame]
// Copyright 2022 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 "src/sys/early_boot_instrumentation/coverage_source.h"
#include <dirent.h>
#include <fcntl.h>
#include <fidl/fuchsia.boot/cpp/wire.h>
#include <fidl/fuchsia.debugdata/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fdio/io.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/fit/defer.h>
#include <lib/stdcompat/source_location.h>
#include <lib/stdcompat/span.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/vfs/cpp/service.h>
#include <lib/zbi-format/internal/debugdata.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zbitl/items/debugdata.h>
#include <lib/zx/channel.h>
#include <lib/zx/eventpair.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/fidl.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <array>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <fbl/unique_fd.h>
#include <sdk/lib/vfs/cpp/pseudo_dir.h>
#include <sdk/lib/vfs/cpp/vmo_file.h>
namespace early_boot_instrumentation {
namespace {
zx::result<> ExportAs(vfs::PseudoDir& out_dir, fbl::unique_fd fd, std::string_view export_as) {
// Get the underlying vmo of the fd.
zx::vmo vmo;
if (auto res = fdio_get_vmo_exact(fd.get(), vmo.reset_and_get_address()); res != ZX_OK) {
FX_LOGS(INFO) << 1;
return zx::error(res);
}
size_t size = 0;
if (auto res = vmo.get_prop_content_size(&size); res != ZX_OK) {
FX_LOGS(INFO) << 2;
return zx::error(res);
}
auto file = std::make_unique<vfs::VmoFile>(std::move(vmo), size);
if (auto res = out_dir.AddEntry(std::string(export_as), std::move(file)); res != ZX_OK) {
FX_LOGS(INFO) << 3;
return zx::error(res);
}
return zx::success();
}
template <typename HandleType>
bool IsSignalled(const HandleType& handle, zx_signals_t signal) {
zx_signals_t actual = 0;
auto status = handle.wait_one(signal, zx::time::infinite_past(), &actual);
return (status == ZX_OK || status == ZX_ERR_TIMED_OUT) && (actual & signal) != 0;
}
enum class DataType {
kDynamic,
kStatic,
};
constexpr std::string_view DataTypeDir(DataType t) {
switch (t) {
case DataType::kDynamic:
return kDynamicDir;
case DataType::kStatic:
return kStaticDir;
default:
return "Unknown DataType.";
}
}
// Returns or creates the respective instance for a given |sink_name|.
vfs::PseudoDir& GetOrCreate(std::string_view sink_name, DataType type, SinkDirMap& sink_map) {
auto it = sink_map.find(sink_name);
// If it's the first time we see this sink, fill up the base hierarchy:
// root
// + /static
// + /dynamic
if (it == sink_map.end()) {
FX_LOGS(INFO) << "Encountered sink " << sink_name << " static and dynamic subdirs created";
it = sink_map.insert(std::make_pair(sink_name, std::make_unique<vfs::PseudoDir>())).first;
it->second->AddEntry(std::string(kStaticDir), std::make_unique<vfs::PseudoDir>());
it->second->AddEntry(std::string(kDynamicDir), std::make_unique<vfs::PseudoDir>());
}
std::string path(DataTypeDir(type));
auto& root_dir = *(it->second);
vfs::Node* node = nullptr;
// Both subdirs should always be available.
ZX_ASSERT(root_dir.Lookup(path, &node) == ZX_OK);
return *reinterpret_cast<vfs::PseudoDir*>(node);
}
template <typename T>
void ForEachDentry(DIR* root, T&& visitor) {
// Borrow underlying FD for opening relative files.
int root_fd = dirfd(root);
while (auto* dentry = readdir(root)) {
std::string_view dentry_name(dentry->d_name);
if (dentry_name == "." || dentry_name == "..") {
continue;
}
fbl::unique_fd entry_fd(openat(root_fd, dentry->d_name, O_RDONLY));
if (!entry_fd) {
FX_LOGS(INFO) << "Failed to obtain FD for " << dentry->d_name << ". " << strerror(errno);
continue;
}
visitor(dentry, std::move(entry_fd));
}
}
} // namespace
zx::result<> ExposeBootDebugdata(fbl::unique_fd& debugdata_root, SinkDirMap& sink_map) {
// Iterate on every entry in the directory.
DIR* root = fdopendir(debugdata_root.get());
if (!root) {
FX_LOGS(INFO) << "Failed to obtain DIR entry from FD. " << strerror(errno);
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Taken by fdopendir.
debugdata_root.release();
auto for_each_debugdata = [&sink_map](std::string_view sink_name, DataType type,
struct dirent* entry, fbl::unique_fd debugdata_fd) {
auto res =
ExportAs(GetOrCreate(sink_name, type, sink_map), std::move(debugdata_fd), entry->d_name);
if (res.is_error()) {
FX_LOGS(ERROR) << "Failed to export boot debugdata to: " << sink_name << "/"
<< DataTypeDir(type) << "/" << entry->d_name;
return;
}
FX_LOGS(INFO) << " Exported boot debugdata to " << sink_name << "/" << DataTypeDir(type) << "/"
<< entry->d_name;
};
// Each sink contains at most two entries, mapped to either static or dynamic data.
// "s" or "d", each of them a directory.
auto for_each_sink = [&for_each_debugdata](struct dirent* entry, fbl::unique_fd sink_dir_fd) {
std::string_view sink_name(entry->d_name);
fbl::unique_fd static_dir_fd(openat(sink_dir_fd.get(), "s", O_DIRECTORY | O_RDONLY));
if (static_dir_fd) {
DIR* static_dir = fdopendir(static_dir_fd.get());
if (static_dir) {
static_dir_fd.release();
ForEachDentry(static_dir, [&](auto* dentry, fbl::unique_fd debugdata_fd) {
for_each_debugdata(sink_name, DataType::kStatic, dentry, std::move(debugdata_fd));
});
closedir(static_dir);
}
}
fbl::unique_fd dynamic_dir_fd(openat(sink_dir_fd.get(), "d", O_DIRECTORY | O_RDONLY));
if (dynamic_dir_fd) {
DIR* dynamic_dir = fdopendir(dynamic_dir_fd.release());
if (dynamic_dir) {
dynamic_dir_fd.release();
ForEachDentry(dynamic_dir, [&](auto* dentry, fbl::unique_fd debugdata_fd) {
for_each_debugdata(sink_name, DataType::kDynamic, dentry, std::move(debugdata_fd));
});
closedir(dynamic_dir);
}
}
};
// Each entry in the root is a directory named after the sink.
ForEachDentry(root, for_each_sink);
closedir(root);
return zx::success();
}
namespace {
constexpr const char* kPublisherPath = fidl::DiscoverableProtocolName<fuchsia_debugdata::Publisher>;
/// Server which forwards requests for the debugdata.Publisher service to the Connect function.
class StashPublisherBridge : public fidl::testing::WireTestBase<fuchsia_io::Directory> {
public:
virtual void Connect(fidl::ServerEnd<fuchsia_debugdata::Publisher> server_end) = 0;
private:
void HandleOpenRequest(std::string_view path, zx::channel channel) {
if (path == kPublisherPath) {
Connect(fidl::ServerEnd<fuchsia_debugdata::Publisher>{std::move(channel)});
} else {
FX_LOGS(WARNING) << "Encountered open request to unhandled path: " << path;
}
}
void Open(OpenRequestView request, OpenCompleter::Sync& completer) final {
HandleOpenRequest(request->path.get(), std::move(request->object));
}
// We use wire test base to avoid churn when new directory methods are added/removed. These
// methods should never be called, since we only support opening a specific path.
void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) final {
FX_LOGS(ERROR) << "Unsupported method: " << name;
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
};
class Server : public StashPublisherBridge,
public fidl::WireServer<fuchsia_boot::SvcStash>,
public fidl::WireServer<fuchsia_debugdata::Publisher> {
public:
explicit Server(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
SinkDirMap TakeSinkToDir() { return std::move(sink_to_dir_); }
private:
void Publish(PublishRequestView request, PublishCompleter::Sync& completer) override {
DataType published_data_type = IsSignalled(request->vmo_token, ZX_EVENTPAIR_PEER_CLOSED)
? DataType::kStatic
: DataType::kDynamic;
auto& dir = GetOrCreate(request->data_sink.get(), published_data_type, sink_to_dir_);
std::array<char, ZX_MAX_NAME_LEN> name_buff = {};
auto name = std::to_string(svc_id_) + "-" + std::to_string(req_id_);
if (zx_status_t status =
request->data.get_property(ZX_PROP_NAME, name_buff.data(), name_buff.size());
status == ZX_OK) {
std::string name_prop(name_buff.data());
if (!name_prop.empty()) {
name += "." + name_prop;
}
}
uint64_t size;
if (zx_status_t status = request->data.get_prop_content_size(&size); status != ZX_OK) {
FX_PLOGS(INFO, status) << "Failed to obtain vmo content size. Attempting to use vmo size.";
if (zx_status_t status = request->data.get_size(&size); status != ZX_OK) {
FX_PLOGS(INFO, status) << "Failed to obtain vmo size.";
size = 0;
}
}
FX_LOGS(INFO) << "Exposing " << request->data_sink.get() << "/"
<< (published_data_type == DataType::kStatic ? "static/" : "dynamic/") << name
<< " size: " << size << " bytes";
dir.AddEntry(std::move(name), std::make_unique<vfs::VmoFile>(std::move(request->data), size));
++req_id_;
}
void Connect(fidl::ServerEnd<fuchsia_debugdata::Publisher> server_end) override {
FX_LOGS(INFO) << "Encountered open request to " << kPublisherPath;
publisher_bindings_.AddBinding(dispatcher_, std::move(server_end), this,
fidl::kIgnoreBindingClosure);
}
void Store(StoreRequestView request, StoreCompleter::Sync& completer) override {
FX_LOGS(INFO) << "Encountered stashed svc handle";
directory_bindings_.AddBinding(dispatcher_, std::move(request->svc_endpoint), this,
[](Server* impl, fidl::UnbindInfo) {
impl->req_id_ = 0;
impl->svc_id_++;
});
}
async_dispatcher_t* const dispatcher_;
SinkDirMap sink_to_dir_;
// used for name generation.
int svc_id_ = 0;
int req_id_ = 0;
fidl::ServerBindingGroup<fuchsia_io::Directory> directory_bindings_;
fidl::ServerBindingGroup<fuchsia_debugdata::Publisher> publisher_bindings_;
};
} // namespace
SinkDirMap ExtractDebugData(fidl::ServerEnd<fuchsia_boot::SvcStash> svc_stash) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
Server server(loop.dispatcher());
fidl::BindServer(loop.dispatcher(), std::move(svc_stash), &server);
loop.RunUntilIdle();
return server.TakeSinkToDir();
}
zx::result<> ExposeDebugDataZbiItem(fidl::ClientEnd<fuchsia_boot::Items> boot_items,
vfs::PseudoDir& log_out_dir, SinkDirMap& sink_map) {
fidl::WireSyncClient<fuchsia_boot::Items> client(std::move(boot_items));
fidl::WireResult wire_result = client->Get2(ZBI_TYPE_DEBUGDATA, nullptr);
if (!wire_result.ok()) {
FX_LOGS(ERROR) << "fuchsia.boot.Items::Get2 Transport FIDL Error: " << wire_result.error();
return zx::error(wire_result.status());
}
if (wire_result->is_error()) {
FX_LOGS(INFO) << "fuchsia.boot.Items::Get2 Protocol Error: " << wire_result->error_value();
return zx::error(wire_result->error_value());
}
auto& items = wire_result->value()->retrieved_items;
if (items.empty()) {
return zx::ok();
}
for (auto& item : items) {
zbi_debugdata_t trailer{};
if (auto res = item.payload.read(&trailer, item.length - sizeof(trailer), sizeof(trailer));
res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to read `zbi_debugdata_t` trailer from VMO. Status " << res;
continue;
}
// Read the vmo name and sink and if there are logs, copy them out to a new vmo.
std::vector<char> names_buffer;
names_buffer.resize(trailer.vmo_name_size + trailer.sink_name_size);
if (auto res =
item.payload.read(names_buffer.data(), trailer.content_size, names_buffer.size());
res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to read names from from ZBI_TYPE_DEBUGDATA VMO. Status " << res;
continue;
}
std::string_view names(names_buffer.data(), names_buffer.size());
std::string_view sink_name = names.substr(0, trailer.sink_name_size);
std::string_view vmo_name = names.substr(sink_name.size(), trailer.vmo_name_size);
auto& vfs_dir = GetOrCreate(sink_name, DataType::kStatic, sink_map);
zx::vmo content_slice;
if (auto res = item.payload.create_child(ZX_VMO_CHILD_SNAPSHOT, 0, trailer.content_size,
&content_slice);
res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create slice of VMO for debugdata content"
<< zx_status_get_string(res);
continue;
}
auto vmo_file = std::make_unique<vfs::VmoFile>(std::move(content_slice), trailer.content_size);
if (auto res = vmo_file->vmo()->set_prop_content_size(trailer.content_size); res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to set VMO's content size. " << zx_status_get_string(res);
}
if (auto res = vmo_file->vmo()->set_size(trailer.content_size); res != ZX_OK) {
if (res == ZX_ERR_UNAVAILABLE) {
FX_LOGS(INFO) << "Provided VMO is not resizable. " << zx_status_get_string(res);
} else {
FX_LOGS(ERROR) << "Failed to set VMO's size. " << zx_status_get_string(res);
}
}
vfs_dir.AddEntry(std::string(vmo_name), std::move(vmo_file));
if (trailer.log_size != 0) {
std::vector<char> logs;
logs.resize(trailer.log_size);
if (auto res = item.payload.read(
logs.data(), trailer.content_size + trailer.sink_name_size + trailer.vmo_name_size,
logs.size());
res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create VMO for ZBI_DEBUGDATA_ITEM logs Name: " << vmo_name
<< " Sink: " << sink_name << " Length: " << trailer.log_size
<< " with status " << res;
continue;
}
zx::vmo log_vmo;
if (auto res = zx::vmo::create(trailer.log_size, 0, &log_vmo); res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create VMO for ZBI_DEBUGDATA_ITEM logs Name: " << vmo_name
<< " Sink: " << sink_name << " Length: " << trailer.log_size
<< " with status " << res;
continue;
}
if (auto res = log_vmo.write(logs.data(), 0, logs.size()); res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create VMO for ZBI_DEBUGDATA_ITEM logs Name: " << vmo_name
<< " Sink: " << sink_name << " Length: " << trailer.log_size
<< " with status " << res;
continue;
}
if (auto res = log_vmo.set_prop_content_size(trailer.log_size); res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to set VMO content size for ZBI_DEBUGDATA_ITEM logs Name: "
<< vmo_name << " Sink: " << sink_name << " Length: " << trailer.log_size
<< " with status " << res;
}
std::string log_file_name = std::string(sink_name) + "-" + std::string(vmo_name) + ".log";
auto vmo_file = std::make_unique<vfs::VmoFile>(std::move(log_vmo), logs.size());
log_out_dir.AddEntry(log_file_name, std::move(vmo_file));
FX_LOGS(INFO) << "Log file " << log_file_name << " exposed on log dir.";
}
}
return zx::ok();
}
zx::result<> ExposeLogs(fbl::unique_fd& boot_logs_dir, vfs::PseudoDir& out_dir) {
if (!boot_logs_dir) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
DIR* root = fdopendir(boot_logs_dir.get());
if (!root) {
FX_LOGS(INFO) << "Failed to obtain DIR entry from FD. " << strerror(errno);
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Taken by fdopendir.
boot_logs_dir.release();
ForEachDentry(root, [&out_dir](struct dirent* entry, fbl::unique_fd fd) {
std::string_view entry_name(entry->d_name);
if (entry->d_type != DT_REG) {
return;
}
if (ExportAs(out_dir, std::move(fd), entry_name).is_ok()) {
FX_LOGS(INFO) << "Log file " << entry_name << " exposed on log dir.";
} else {
FX_LOGS(INFO) << "Log file " << entry_name << " found but failed to be exposed.";
}
});
closedir(root);
return zx::ok();
}
} // namespace early_boot_instrumentation