| // 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 <fcntl.h> |
| #include <fuchsia/boot/c/fidl.h> |
| #include <fuchsia/debugdata/c/fidl.h> |
| #include <fuchsia/io/cpp/fidl.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/stdcompat/source_location.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/vfs/cpp/service.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 <mutex> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #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 { |
| |
| constexpr std::string_view kKernelProfRaw = "zircon.elf.profraw"; |
| constexpr std::string_view kKernelSymbolizerLog = "symbolizer.log"; |
| |
| constexpr std::string_view kPhysbootProfRaw = "physboot.profraw"; |
| constexpr std::string_view kPhysbootSymbolizerLog = "symbolizer.log"; |
| |
| struct ExportedFd { |
| fbl::unique_fd fd; |
| std::string export_name; |
| }; |
| |
| zx::result<> Export(vfs::PseudoDir& out_dir, cpp20::span<ExportedFd> exported_fds) { |
| for (const auto& [fd, export_as] : exported_fds) { |
| // 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) { |
| return zx::error(res); |
| } |
| size_t size = 0; |
| if (auto res = vmo.get_size(&size); res != ZX_OK) { |
| return zx::error(res); |
| } |
| |
| auto file = std::make_unique<vfs::VmoFile>(std::move(vmo), size); |
| if (auto res = out_dir.AddEntry(export_as, std::move(file)); res != ZX_OK) { |
| return zx::error(res); |
| } |
| } |
| return zx::success(); |
| } |
| |
| // TODO(fxbug.dev/82681): Clean up manual FIDL definitions once there exists a stable way of doing |
| // this. |
| struct fuchsia_io_DirectoryOpenRequest { |
| FIDL_ALIGNDECL |
| fidl_message_header_t hdr; |
| uint32_t flags; |
| uint32_t mode; |
| fidl_string_t path; |
| zx_handle_t object; |
| }; |
| |
| struct OpenData { |
| std::string path; |
| zx::unowned_channel service_request; |
| }; |
| |
| zx::result<OpenData> GetOpenData(cpp20::span<uint8_t> message, cpp20::span<zx_handle_t> handles) { |
| if (message.size() < sizeof(fuchsia_io_DirectoryOpenRequest)) { |
| return zx::error(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| |
| if (handles.size() != 1) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| auto* open_rq = reinterpret_cast<fuchsia_io_DirectoryOpenRequest*>(message.data()); |
| |
| return zx::ok( |
| OpenData{.path = std::string(reinterpret_cast<char*>(message.data() + sizeof(*open_rq)), |
| open_rq->path.size), |
| .service_request = zx::unowned_channel(handles[0])}); |
| } |
| |
| struct PublishedData { |
| std::string sink; |
| zx::vmo data; |
| zx::eventpair token; |
| size_t content_size; |
| }; |
| |
| zx::result<PublishedData> GetPublishedData(cpp20::span<uint8_t> message, |
| cpp20::span<zx_handle_t> handles) { |
| if (message.size() < sizeof(fuchsia_debugdata_PublisherPublishRequestMessage)) { |
| return zx::error(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| auto* publish_rq = |
| reinterpret_cast<fuchsia_debugdata_PublisherPublishRequestMessage*>(message.data()); |
| |
| if (handles.size() != 2) { |
| return zx::error(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| |
| zx::vmo data(std::exchange(handles[0], ZX_HANDLE_INVALID)); |
| size_t size = 0; |
| |
| if (auto res = data.get_prop_content_size(&size); res != ZX_OK) { |
| FX_LOGS(INFO) << "Failed to obtain vmo content size. Attempting to use vmo size."; |
| } |
| |
| if (size == 0) { |
| if (auto res = data.get_size(&size); res != ZX_OK) { |
| FX_LOGS(INFO) << "Failed to obtain vmo size."; |
| return zx::error(res); |
| } |
| } |
| |
| return zx::ok(PublishedData{ |
| .sink = std::string(reinterpret_cast<char*>(message.data()) + sizeof(*publish_rq), |
| publish_rq->data_sink.size), |
| .data = std::move(data), |
| .token = zx::eventpair(handles[1]), |
| .content_size = size, |
| }); |
| } |
| |
| 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; |
| } |
| |
| struct ChannelMessageInfo { |
| uint32_t outstanding_bytes = 0; |
| uint32_t outstanding_handles = 0; |
| }; |
| |
| zx::result<ChannelMessageInfo> GetChannelOutstandingBytesAndHandles(const zx::channel& channel) { |
| ChannelMessageInfo info; |
| if (auto res = channel.read(0, nullptr, nullptr, 0, 0, &info.outstanding_bytes, |
| &info.outstanding_handles); |
| res != ZX_OK && res != ZX_ERR_BUFFER_TOO_SMALL) { |
| return zx::error(res); |
| } |
| return zx::ok(info); |
| } |
| |
| // MessageVisitor is a Callable with a signature of (status, bytes, handles). |
| template <typename MessageVisitor> |
| void OnEachMessage(zx::unowned_channel& src, MessageVisitor visitor) { |
| static_assert(std::is_invocable_v<MessageVisitor, zx_status_t, cpp20::span<uint8_t>, |
| cpp20::span<zx_handle_t>>); |
| if (!src->is_valid()) { |
| visitor(ZX_ERR_BAD_HANDLE, cpp20::span<uint8_t>{}, cpp20::span<zx_handle_t>{}); |
| return; |
| } |
| |
| std::vector<uint8_t> bytes; |
| std::vector<zx_handle_t> handles; |
| while (IsSignalled(*src, ZX_CHANNEL_READABLE)) { |
| auto res = GetChannelOutstandingBytesAndHandles(*src); |
| if (res.is_error()) { |
| visitor(res.status_value(), cpp20::span<uint8_t>{}, cpp20::span<zx_handle_t>{}); |
| break; |
| } |
| |
| auto [byte_count, handle_count] = *res; |
| bytes.resize(byte_count); |
| handles.resize(handle_count); |
| uint32_t actual_bytes, actual_handles; |
| auto status = src->read(0, bytes.data(), handles.data(), byte_count, handle_count, |
| &actual_bytes, &actual_handles); |
| visitor(status, cpp20::span(bytes), cpp20::span(handles)); |
| if (status != ZX_OK) { |
| break; |
| } |
| zx_handle_close_many(handles.data(), handle_count); |
| } |
| } |
| |
| enum class DataType { |
| kDynamic, |
| kStatic, |
| }; |
| |
| // 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(type == DataType::kDynamic ? kDynamicDir : kStaticDir); |
| |
| auto& root_dir = *(it->second); |
| vfs::internal::Node* node = nullptr; |
| // Both subdirs should always be available. |
| ZX_ASSERT(root_dir.Lookup(path, &node) == ZX_OK); |
| ZX_ASSERT(node->IsDirectory()); |
| return *reinterpret_cast<vfs::PseudoDir*>(node); |
| } |
| |
| } // namespace |
| |
| zx::result<> ExposeKernelProfileData(fbl::unique_fd& kernel_data_dir, SinkDirMap& sink_map) { |
| std::vector<ExportedFd> exported_fds; |
| |
| fbl::unique_fd kernel_profile(openat(kernel_data_dir.get(), kKernelProfRaw.data(), O_RDONLY)); |
| if (!kernel_profile) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| exported_fds.emplace_back( |
| ExportedFd{.fd = std::move(kernel_profile), .export_name = std::string(kKernelFile)}); |
| FX_LOGS(INFO) << "Exposing " << kKernelFile; |
| |
| fbl::unique_fd kernel_log(openat(kernel_data_dir.get(), kKernelSymbolizerLog.data(), O_RDONLY)); |
| if (kernel_log) { |
| FX_LOGS(INFO) << "Exposing " << kKernelSymbolizerFile; |
| exported_fds.emplace_back( |
| ExportedFd{.fd = std::move(kernel_log), .export_name = std::string(kKernelSymbolizerFile)}); |
| } |
| |
| return Export(GetOrCreate(kLlvmSink, DataType::kDynamic, sink_map), exported_fds); |
| } |
| |
| zx::result<> ExposePhysbootProfileData(fbl::unique_fd& physboot_data_dir, SinkDirMap& sink_map) { |
| std::vector<ExportedFd> exported_fds; |
| |
| fbl::unique_fd phys_profile(openat(physboot_data_dir.get(), kPhysbootProfRaw.data(), O_RDONLY)); |
| if (!phys_profile) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| exported_fds.emplace_back( |
| ExportedFd{.fd = std::move(phys_profile), .export_name = std::string(kPhysFile)}); |
| FX_LOGS(INFO) << "Exposing " << kPhysFile; |
| |
| fbl::unique_fd phys_log(openat(physboot_data_dir.get(), kPhysbootSymbolizerLog.data(), O_RDONLY)); |
| if (phys_log) { |
| FX_LOGS(INFO) << "Exposing " << kPhysSymbolizerFile; |
| exported_fds.emplace_back( |
| ExportedFd{.fd = std::move(phys_log), .export_name = std::string(kPhysSymbolizerFile)}); |
| } |
| |
| return Export(GetOrCreate(kLlvmSink, DataType::kStatic, sink_map), exported_fds); |
| } |
| |
| SinkDirMap ExtractDebugData(zx::unowned_channel svc_stash) { |
| SinkDirMap sink_to_dir; |
| |
| // Results from a publish request. |
| std::vector<PublishedData> published_data; |
| |
| // used for name generation. |
| int svc_id = 0; |
| int req_id = 0; |
| |
| auto on_publish_request = [&sink_to_dir, &svc_id, &req_id](zx_status_t status, auto bytes, |
| auto handles) { |
| if (status != ZX_OK) { |
| FX_LOGS(INFO) << "Encountered error status while processing open requests " |
| << zx_status_get_string(status); |
| return; |
| } |
| |
| if (auto published_data_or = GetPublishedData(bytes, handles); published_data_or.is_ok()) { |
| auto& [sink, vmo, token, content_size] = *published_data_or; |
| DataType published_data_type = |
| IsSignalled(token, ZX_EVENTPAIR_PEER_CLOSED) ? DataType::kStatic : DataType::kDynamic; |
| auto& dir = GetOrCreate(sink, 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 (auto res = vmo.get_property(ZX_PROP_NAME, name_buff.data(), name_buff.size()); |
| res == ZX_OK) { |
| std::string name_prop(name_buff.data()); |
| if (!name_prop.empty()) { |
| name += "." + name_prop; |
| } |
| } |
| FX_LOGS(INFO) << "Exposing " << sink << "/" |
| << (published_data_type == DataType::kStatic ? "static/" : "dynamic/") << name |
| << " size: " << content_size << " bytes"; |
| dir.AddEntry(std::move(name), std::make_unique<vfs::VmoFile>(std::move(vmo), content_size)); |
| ++req_id; |
| } else { |
| FX_LOGS(INFO) << "Encountered error(" << published_data_or.status_string() |
| << " while parsing publish request. Skipping entry."; |
| } |
| }; |
| |
| auto on_open_request = [&on_publish_request](zx_status_t status, auto bytes, auto handles) { |
| if (status != ZX_OK) { |
| FX_LOGS(INFO) << "Encountered error status while processing open requests " |
| << zx_status_get_string(status); |
| return; |
| } |
| |
| if (handles.size() != 1) { |
| FX_LOGS(INFO) << "Stashed svc contains invalid handle."; |
| return; |
| } |
| if (auto open_data_or = GetOpenData(bytes, handles); open_data_or.is_ok()) { |
| auto& [path, service_request] = *open_data_or; |
| if (path == fuchsia_debugdata_Publisher_Name) { |
| FX_LOGS(INFO) << "Encountered open request to debugdata.Publisher"; |
| zx::unowned_channel service_request(handles[0]); |
| OnEachMessage(service_request, on_publish_request); |
| } else { |
| FX_LOGS(INFO) << "Encountered open request to unhandled path " << path; |
| } |
| } else { |
| FX_LOGS(INFO) << "Encountered error(" << open_data_or.status_string() |
| << " while parsing open request. Skipping entry."; |
| } |
| }; |
| |
| auto on_stashed_svc = [&on_open_request, &req_id, &svc_id](zx_status_t status, auto bytes, |
| auto handles) { |
| if (status != ZX_OK) { |
| FX_LOGS(INFO) << "Encountered error status while processing open requests " |
| << zx_status_get_string(status); |
| return; |
| } |
| if (handles.size() != 1) { |
| FX_LOGS(INFO) << "No stashed handle on svc stashed channel message. Skipping."; |
| return; |
| } |
| if (bytes.size() < sizeof(fuchsia_boot_SvcStashStoreRequestMessage)) { |
| FX_LOGS(INFO) << "SvcStash/Store request message expected, but message too small. Skipping."; |
| return; |
| } |
| |
| auto* req = reinterpret_cast<fuchsia_boot_SvcStashStoreRequestMessage*>(bytes.data()); |
| // Small verification that the fidl header is what we expect. |
| if (req->hdr.magic_number != kFidlWireFormatMagicNumberInitial || |
| req->hdr.ordinal != fuchsia_boot_SvcStashStoreOrdinal) { |
| FX_LOGS(INFO) << "SvcStash/Push request message expected, but message header could not be " |
| "verified. Skipping."; |
| return; |
| } |
| |
| zx::unowned_channel stashed_svc(handles[0]); |
| FX_LOGS(INFO) << " Encountered stashed svc handle"; |
| OnEachMessage(stashed_svc, on_open_request); |
| req_id = 0; |
| svc_id++; |
| }; |
| |
| OnEachMessage(svc_stash, on_stashed_svc); |
| return sink_to_dir; |
| } |
| |
| } // namespace early_boot_instrumentation |