| // Copyright 2016 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. |
| |
| // TODO(dje): wip wip wip |
| |
| #include "control.h" |
| |
| #include <fcntl.h> |
| #include <link.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <cinttypes> |
| |
| #include <iostream> |
| |
| #include <lib/zircon-internal/device/cpu-trace/intel-pt.h> |
| #include <zircon/device/ktrace.h> |
| #include <zircon/syscalls.h> |
| |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/zircon-internal/ktrace.h> |
| #include <lib/zx/handle.h> |
| #include <lib/zx/vmo.h> |
| |
| #include "src/lib/files/unique_fd.h" |
| #include "src/lib/fxl/logging.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| #include "garnet/lib/debugger_utils/util.h" |
| #include "garnet/lib/debugger_utils/x86_pt.h" |
| |
| #include "garnet/lib/inferior_control/arch.h" |
| #include "garnet/lib/inferior_control/arch_x64.h" |
| |
| #include "server.h" |
| |
| namespace insntrace { |
| |
| static constexpr char ipt_device_path[] = "/dev/sys/cpu-trace/insntrace"; |
| static constexpr char ktrace_device_path[] = "/dev/misc/ktrace"; |
| |
| static constexpr char buffer_output_path_suffix[] = "pt"; |
| static constexpr char ktrace_output_path_suffix[] = "ktrace"; |
| static constexpr char cpuid_output_path_suffix[] = "cpuid"; |
| static constexpr char pt_list_output_path_suffix[] = "ptlist"; |
| |
| static constexpr uint32_t kKtraceGroupMask = KTRACE_GRP_ARCH | KTRACE_GRP_TASKS; |
| |
| static bool OpenDevices(fxl::UniqueFD* out_ipt_fd, fxl::UniqueFD* out_ktrace_fd, |
| zx::handle* out_ktrace_handle) { |
| int ipt_fd = -1; |
| int ktrace_fd = -1; |
| zx_handle_t ktrace_handle = ZX_HANDLE_INVALID; |
| |
| if (out_ipt_fd) { |
| ipt_fd = open(ipt_device_path, O_RDONLY); |
| if (ipt_fd < 0) { |
| FXL_LOG(ERROR) << "unable to open " << ipt_device_path << ": " |
| << debugger_utils::ErrnoString(errno); |
| return false; |
| } |
| } |
| |
| if (out_ktrace_fd || out_ktrace_handle) { |
| ktrace_fd = open(ktrace_device_path, O_RDONLY); |
| if (ktrace_fd < 0) { |
| FXL_LOG(ERROR) << "open ktrace" |
| << ", " << debugger_utils::ErrnoString(errno); |
| close(ipt_fd); |
| return false; |
| } |
| } |
| |
| if (out_ktrace_handle) { |
| ssize_t ssize = ioctl_ktrace_get_handle(ktrace_fd, &ktrace_handle); |
| if (ssize != sizeof(ktrace_handle)) { |
| FXL_LOG(ERROR) << "get ktrace handle" |
| << ", " << debugger_utils::ErrnoString(errno); |
| close(ipt_fd); |
| close(ktrace_fd); |
| return false; |
| } |
| } |
| |
| if (out_ipt_fd) { |
| out_ipt_fd->reset(ipt_fd); |
| } |
| if (out_ktrace_fd) { |
| out_ktrace_fd->reset(ktrace_fd); |
| } else if (ktrace_fd != -1) { |
| // Only needed to get ktrace handle. |
| close(ktrace_fd); |
| } |
| if (out_ktrace_handle) { |
| out_ktrace_handle->reset(ktrace_handle); |
| } |
| |
| return true; |
| } |
| |
| bool AllocTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "AllocTrace called"; |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return false; |
| |
| ioctl_insntrace_trace_config_t trace_config; |
| trace_config.mode = config.mode; |
| trace_config.num_traces = (config.mode == IPT_MODE_CPUS |
| ? config.num_cpus |
| : config.max_threads); |
| ssize_t ssize = ioctl_insntrace_alloc_trace(ipt_fd.get(), &trace_config); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "set perf mode: " << debugger_utils::ZxErrorString(ssize); |
| goto Fail; |
| } |
| |
| return true; |
| |
| Fail: |
| return false; |
| } |
| |
| static void InitIptBufferConfig(ioctl_insntrace_buffer_config_t* ipt_config, |
| const IptConfig& config) { |
| memset(ipt_config, 0, sizeof(*ipt_config)); |
| ipt_config->num_chunks = config.num_chunks; |
| ipt_config->chunk_order = config.chunk_order; |
| ipt_config->is_circular = config.is_circular; |
| ipt_config->ctl = config.CtlMsr(); |
| ipt_config->cr3_match = config.cr3_match; |
| ipt_config->addr_ranges[0].a = config.AddrBegin(0); |
| ipt_config->addr_ranges[0].b = config.AddrEnd(0); |
| ipt_config->addr_ranges[1].a = config.AddrBegin(1); |
| ipt_config->addr_ranges[1].b = config.AddrEnd(1); |
| } |
| |
| bool InitTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "InitTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_CPUS); |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return false; |
| |
| for (uint32_t cpu = 0; cpu < config.num_cpus; ++cpu) { |
| ioctl_insntrace_buffer_config_t ipt_config; |
| InitIptBufferConfig(&ipt_config, config); |
| |
| uint32_t descriptor; |
| auto ssize = ioctl_insntrace_alloc_buffer(ipt_fd.get(), &ipt_config, &descriptor); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "init cpu perf: " |
| << debugger_utils::ZxErrorString(ssize); |
| goto Fail; |
| } |
| // Buffers are automagically assigned to cpus, descriptor == cpu#, |
| // so we can just ignore descriptor here. |
| } |
| |
| return true; |
| |
| Fail: |
| return false; |
| } |
| |
| bool InitThreadTrace(inferior_control::Thread* thread, const IptConfig& config) { |
| FXL_LOG(INFO) << "InitThreadTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_THREADS); |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return false; |
| |
| ioctl_insntrace_buffer_config_t ipt_config; |
| InitIptBufferConfig(&ipt_config, config); |
| |
| uint32_t descriptor; |
| ssize_t ssize = |
| ioctl_insntrace_alloc_buffer(ipt_fd.get(), &ipt_config, &descriptor); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "init thread perf: " |
| << debugger_utils::ZxErrorString(ssize); |
| goto Fail; |
| } |
| |
| thread->set_ipt_buffer(descriptor); |
| return true; |
| |
| Fail: |
| return false; |
| } |
| |
| // This must be called before a process is started so we emit a ktrace |
| // process start record for it. |
| |
| bool InitProcessTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "InitProcessTrace called"; |
| |
| zx::handle ktrace_handle; |
| zx_status_t status; |
| |
| if (!OpenDevices(nullptr, nullptr, &ktrace_handle)) |
| return false; |
| |
| // If tracing cpus we may want all the records for processes that were |
| // started during boot, so don't reset ktrace here. If tracing threads it |
| // doesn't much matter other than hopefully the necessary records don't get |
| // over run, which is handled below by only enabling the collection groups |
| // we need. So for now leave existing records alone. |
| // We also need to make the distinction of ktrace records for processes |
| // started during boot (they can appear a fair bit in traces), and random |
| // processes that were started later that have nothing to do with what we |
| // want to collect. IWBN to capture the ktrace records from boot and save |
| // them away. Then it'd make more sense to rewind here, though even then |
| // the user may have started something important before we get run. Another |
| // thought is to provide an action to control ktrace specifically. |
| #if 0 // TODO(dje) |
| if (config.mode == IPT_MODE_THREADS) { |
| status = zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_STOP, 0, |
| nullptr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "ktrace stop: " << debugger_utils::ZxErrorString(status); |
| goto Fail; |
| } |
| status = zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_REWIND, 0, |
| nullptr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "ktrace rewind: " << debugger_utils::ZxErrorString(status); |
| goto Fail; |
| } |
| } |
| #endif |
| |
| // We definitely need ktrace turned on in order to get cr3->pid mappings, |
| // which we need to map trace cr3 values to ld.so mappings, which we need in |
| // order to be able to find the ELFs, which are required by the decoder. |
| // So this isn't a nice-to-have, we need it. It's possible ktrace is |
| // currently off, so ensure it's turned on. |
| // For now just include arch info in the ktrace - we need it, and we don't |
| // want to risk the ktrace buffer filling without it. |
| // Also include task info to get process exit records - we need to know when |
| // a cr3 value becomes invalid. Hopefully this won't cause the buffer to |
| // overrun. It it does we could consider having special ktrace records just |
| // for this, but that's a last resort kind of thing. |
| status = zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_START, |
| kKtraceGroupMask, nullptr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "ktrace start: " << debugger_utils::ZxErrorString(status); |
| goto Fail; |
| } |
| |
| return true; |
| |
| Fail: |
| // TODO(dje): Resume original ktracing? Need ability to get old value. |
| // For now set the values to what we need: A later run might still need |
| // the boot time records. |
| zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_STOP, 0, nullptr); |
| zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_START, kKtraceGroupMask, |
| nullptr); |
| |
| return false; |
| } |
| |
| bool StartTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "StartTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_CPUS); |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return false; |
| |
| ssize_t ssize = ioctl_insntrace_start(ipt_fd.get()); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "start cpu perf: " |
| << debugger_utils::ZxErrorString(ssize); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool StartThreadTrace(inferior_control::Thread* thread, |
| const IptConfig& config) { |
| FXL_LOG(INFO) << "StartThreadTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_THREADS); |
| |
| if (thread->ipt_buffer() < 0) { |
| FXL_LOG(INFO) << fxl::StringPrintf("Thread %" PRIu64 " has no IPT buffer", |
| thread->id()); |
| // TODO(dje): For now. This isn't an error in the normal sense. |
| return true; |
| } |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return false; |
| |
| ioctl_insntrace_assign_thread_buffer_t assign; |
| zx_status_t status; |
| ssize_t ssize; |
| |
| status = zx_handle_duplicate(thread->handle(), ZX_RIGHT_SAME_RIGHTS, |
| &assign.thread); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "duplicating thread handle: " |
| << debugger_utils::ZxErrorString(status); |
| goto Fail; |
| } |
| assign.descriptor = thread->ipt_buffer(); |
| ssize = ioctl_insntrace_assign_thread_buffer(ipt_fd.get(), &assign); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "assigning ipt buffer to thread: " |
| << debugger_utils::ZxErrorString(ssize); |
| goto Fail; |
| } |
| |
| return true; |
| |
| Fail: |
| return false; |
| } |
| |
| void StopTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "StopTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_CPUS); |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return; |
| |
| ssize_t ssize = ioctl_insntrace_stop(ipt_fd.get()); |
| if (ssize < 0) { |
| // TODO(dje): This is really bad, this shouldn't fail. |
| FXL_LOG(ERROR) << "stop cpu perf: " << debugger_utils::ZxErrorString(ssize); |
| } |
| } |
| |
| void StopThreadTrace(inferior_control::Thread* thread, const IptConfig& config) { |
| FXL_LOG(INFO) << "StopThreadTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_THREADS); |
| |
| if (thread->ipt_buffer() < 0) { |
| FXL_LOG(INFO) << fxl::StringPrintf("Thread %" PRIu64 " has no IPT buffer", |
| thread->id()); |
| return; |
| } |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return; |
| |
| ioctl_insntrace_assign_thread_buffer_t assign; |
| zx_handle_t status; |
| ssize_t ssize; |
| |
| status = zx_handle_duplicate(thread->handle(), ZX_RIGHT_SAME_RIGHTS, |
| &assign.thread); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "duplicating thread handle: " |
| << debugger_utils::ZxErrorString(status); |
| goto Fail; |
| } |
| assign.descriptor = thread->ipt_buffer(); |
| ssize = ioctl_insntrace_release_thread_buffer(ipt_fd.get(), &assign); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "releasing ipt buffer from thread: " |
| << debugger_utils::ZxErrorString(ssize); |
| goto Fail; |
| } |
| |
| Fail:; // nothing to do |
| } |
| |
| void StopSidebandDataCollection(const IptConfig& config) { |
| FXL_LOG(INFO) << "StopSidebandDataCollection called"; |
| |
| zx::handle ktrace_handle; |
| if (!OpenDevices(nullptr, nullptr, &ktrace_handle)) |
| return; |
| |
| // Avoid having the records we need overrun by the time we collect them by |
| // stopping ktrace here. It will get turned back on by "reset". |
| zx_status_t status = |
| zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_STOP, 0, nullptr); |
| if (status != ZX_OK) { |
| // TODO(dje): This shouldn't fail either, should it? |
| FXL_LOG(ERROR) << "stop ktrace: " << debugger_utils::ZxErrorString(status); |
| } |
| } |
| |
| static std::string GetCpuPtFileName(const std::string& output_path_prefix, |
| uint64_t id) { |
| const char* name_prefix = "cpu"; |
| return fxl::StringPrintf("%s.%s%" PRIu64 ".%s", output_path_prefix.c_str(), |
| name_prefix, id, buffer_output_path_suffix); |
| } |
| |
| static std::string GetThreadPtFileName(const std::string& output_path_prefix, |
| uint64_t id) { |
| const char* name_prefix = "thr"; |
| return fxl::StringPrintf("%s.%s%" PRIu64 ".%s", output_path_prefix.c_str(), |
| name_prefix, id, buffer_output_path_suffix); |
| } |
| |
| // Write the contents of buffer |descriptor| to a file. |
| // The file's name is $output_path_prefix.$name_prefix$id.pt. |
| |
| static zx_status_t WriteBufferData(const IptConfig& config, |
| const fxl::UniqueFD& ipt_fd, |
| uint32_t descriptor, uint64_t id) { |
| std::string output_path; |
| if (config.mode == IPT_MODE_CPUS) |
| output_path = GetCpuPtFileName(config.output_path_prefix, id); |
| else |
| output_path = GetThreadPtFileName(config.output_path_prefix, id); |
| const char* c_path = output_path.c_str(); |
| |
| zx_status_t status = ZX_OK; |
| |
| // Refetch the buffer config as we can be invoked in a separate process, |
| // after tracing has started, and shouldn't rely on what the user thinks |
| // the config is. |
| ioctl_insntrace_buffer_config_t buffer_config; |
| ssize_t ssize = |
| ioctl_insntrace_get_buffer_config(ipt_fd.get(), &descriptor, &buffer_config); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << fxl::StringPrintf( |
| "ioctl_insntrace_get_buffer_config: buffer %u: ", |
| descriptor) |
| << debugger_utils::ZxErrorString(ssize); |
| return ssize; |
| } |
| |
| ioctl_insntrace_buffer_info_t info; |
| ssize = ioctl_insntrace_get_buffer_info(ipt_fd.get(), &descriptor, &info); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << fxl::StringPrintf( |
| "ioctl_insntrace_get_buffer_info: buffer %u: ", descriptor) |
| << debugger_utils::ZxErrorString(ssize); |
| return ssize; |
| } |
| |
| fxl::UniqueFD fd(open(c_path, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR)); |
| if (!fd.is_valid()) { |
| FXL_LOG(ERROR) << fxl::StringPrintf("unable to write file: %s", c_path) |
| << ", " << debugger_utils::ErrnoString(errno); |
| return ZX_ERR_BAD_PATH; |
| } |
| |
| // TODO(dje): Fetch from vmo? |
| size_t chunk_size = (1 << buffer_config.chunk_order) * PAGE_SIZE; |
| uint32_t num_chunks = buffer_config.num_chunks; |
| |
| // If using a circular buffer there's (currently) no way to know if |
| // tracing wrapped, so for now we just punt and always dump the entire |
| // buffer. It's highly likely it wrapped anyway. |
| size_t bytes_left; |
| if (buffer_config.is_circular) |
| bytes_left = num_chunks * chunk_size; |
| else |
| bytes_left = info.capture_end; |
| |
| FXL_LOG(INFO) << fxl::StringPrintf("Writing %zu bytes to %s", bytes_left, |
| c_path); |
| |
| char buf[4096]; |
| |
| for (uint32_t i = 0; i < num_chunks && bytes_left > 0; ++i) { |
| ioctl_insntrace_chunk_handle_req_t handle_rqst; |
| handle_rqst.descriptor = descriptor; |
| handle_rqst.chunk_num = i; |
| zx_handle_t vmo_handle; |
| ssize = ioctl_insntrace_get_chunk_handle(ipt_fd.get(), &handle_rqst, &vmo_handle); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) |
| << fxl::StringPrintf( |
| "ioctl_insntrace_get_buffer_handle: buffer %u, buffer %u: ", |
| descriptor, i) |
| << debugger_utils::ZxErrorString(ssize); |
| goto Fail; |
| } |
| zx::vmo vmo(vmo_handle); |
| |
| size_t buffer_remaining = chunk_size; |
| size_t offset = 0; |
| while (buffer_remaining && bytes_left) { |
| size_t to_write = sizeof(buf); |
| if (to_write > buffer_remaining) |
| to_write = buffer_remaining; |
| if (to_write > bytes_left) |
| to_write = bytes_left; |
| // TODO(dje): Mapping into process and reading directly from that |
| // left for another day. |
| status = vmo.read(buf, offset, to_write); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << fxl::StringPrintf( |
| "zx_vmo_read: buffer %u, buffer %u, offset %zu: ", |
| descriptor, i, offset) |
| << debugger_utils::ZxErrorString(status); |
| goto Fail; |
| } |
| if (write(fd.get(), buf, to_write) != (ssize_t)to_write) { |
| FXL_LOG(ERROR) << fxl::StringPrintf("short write, file: %s\n", c_path); |
| status = ZX_ERR_IO; |
| goto Fail; |
| } |
| offset += to_write; |
| buffer_remaining -= to_write; |
| bytes_left -= to_write; |
| } |
| } |
| |
| assert(bytes_left == 0); |
| status = ZX_OK; |
| // fallthrough |
| |
| Fail: |
| // We don't delete the file on failure on purpose, it is kept for |
| // debugging purposes. |
| return status; |
| } |
| |
| // Write all output files. |
| // This assumes tracing has already been stopped. |
| |
| void DumpTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "DumpTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_CPUS); |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return; |
| |
| for (uint32_t cpu = 0; cpu < config.num_cpus; ++cpu) { |
| // Buffer descriptors for cpus is the cpu number. |
| auto status = WriteBufferData(config, ipt_fd, cpu, cpu); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << fxl::StringPrintf("dump perf of cpu %u: ", cpu) |
| << debugger_utils::ZxErrorString(status); |
| // Keep trying to dump other cpu's data. |
| } |
| } |
| } |
| |
| // Write the buffer contents for |thread|. |
| // This assumes the thread is stopped. |
| |
| void DumpThreadTrace(inferior_control::Thread* thread, const IptConfig& config) { |
| FXL_LOG(INFO) << "DumpThreadTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_THREADS); |
| |
| zx_koid_t id = thread->id(); |
| |
| if (thread->ipt_buffer() < 0) { |
| FXL_LOG(INFO) << fxl::StringPrintf("Thread %" PRIu64 " has no IPT buffer", |
| id); |
| return; |
| } |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return; |
| |
| auto status = WriteBufferData(config, ipt_fd, thread->ipt_buffer(), id); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << fxl::StringPrintf("dump perf of thread %" PRIu64 ": ", id) |
| << debugger_utils::ZxErrorString(status); |
| } |
| } |
| |
| void DumpSidebandData(const IptConfig& config) { |
| FXL_LOG(INFO) << "DumpSidebandData called"; |
| |
| { |
| fxl::UniqueFD ktrace_fd; |
| if (!OpenDevices(nullptr, &ktrace_fd, nullptr)) |
| return; |
| |
| std::string ktrace_output_path = fxl::StringPrintf( |
| "%s.%s", config.output_path_prefix.c_str(), ktrace_output_path_suffix); |
| const char* ktrace_c_path = ktrace_output_path.c_str(); |
| |
| fxl::UniqueFD dest_fd( |
| open(ktrace_c_path, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR)); |
| if (dest_fd.is_valid()) { |
| ssize_t count; |
| char buf[1024]; |
| while ((count = read(ktrace_fd.get(), buf, sizeof(buf))) != 0) { |
| if (write(dest_fd.get(), buf, count) != count) { |
| FXL_LOG(ERROR) << "error writing " << ktrace_c_path; |
| } |
| } |
| } else { |
| FXL_LOG(ERROR) << fxl::StringPrintf("unable to create %s", ktrace_c_path) |
| << ", " << debugger_utils::ErrnoString(errno); |
| } |
| } |
| |
| // TODO(dje): UniqueFILE? |
| { |
| std::string cpuid_output_path = fxl::StringPrintf( |
| "%s.%s", config.output_path_prefix.c_str(), cpuid_output_path_suffix); |
| const char* cpuid_c_path = cpuid_output_path.c_str(); |
| |
| FILE* f = fopen(cpuid_c_path, "w"); |
| if (f != nullptr) { |
| inferior_control::DumpArch(f); |
| // Also put the mtc_freq value in the cpuid file, it's as good a place |
| // for it as any. See intel-pt.h:pt_config. |
| // Alternatively this could be added to the ktrace record. |
| // TODO(dje): Put constants in zircon/device/intel-pt.h. |
| unsigned mtc_freq = config.mtc_freq; |
| fprintf(f, "mtc_freq: %u\n", mtc_freq); |
| // TODO(dje): verify writes succeed |
| fclose(f); |
| } else { |
| FXL_LOG(ERROR) << "unable to write PT config to " << cpuid_c_path; |
| } |
| } |
| |
| // TODO(dje): UniqueFILE? |
| // TODO(dje): Handle IPT_MODE_THREADS |
| if (config.mode == IPT_MODE_CPUS) { |
| std::string pt_list_output_path = fxl::StringPrintf( |
| "%s.%s", config.output_path_prefix.c_str(), pt_list_output_path_suffix); |
| const char* pt_list_c_path = pt_list_output_path.c_str(); |
| |
| FILE* f = fopen(pt_list_c_path, "w"); |
| if (f != nullptr) { |
| for (uint32_t cpu = 0; cpu < config.num_cpus; ++cpu) { |
| std::string pt_file = GetCpuPtFileName(config.output_path_prefix, cpu); |
| fprintf(f, "%u %s\n", cpu, pt_file.c_str()); |
| } |
| // TODO(dje): verify writes succeed |
| fclose(f); |
| } else { |
| FXL_LOG(ERROR) << "unable to write PT list to " << pt_list_c_path; |
| } |
| } |
| } |
| |
| void ResetTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "ResetTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_CPUS); |
| |
| // TODO(dje): Nothing to do currently. There use to be. So keep this |
| // function around for a bit. |
| } |
| |
| void ResetThreadTrace(inferior_control::Thread* thread, |
| const IptConfig& config) { |
| FXL_LOG(INFO) << "ResetThreadTrace called"; |
| FXL_DCHECK(config.mode == IPT_MODE_THREADS); |
| |
| if (thread->ipt_buffer() < 0) { |
| FXL_LOG(INFO) << fxl::StringPrintf("Thread %" PRIu64 " has no IPT buffer", |
| thread->id()); |
| return; |
| } |
| |
| fxl::UniqueFD ipt_fd; |
| if (!OpenDevices(&ipt_fd, nullptr, nullptr)) |
| return; |
| |
| uint32_t descriptor = thread->ipt_buffer(); |
| ssize_t ssize = ioctl_insntrace_free_buffer(ipt_fd.get(), &descriptor); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "freeing ipt buffer: " |
| << debugger_utils::ZxErrorString(ssize); |
| goto Fail; |
| } |
| |
| Fail: |
| thread->set_ipt_buffer(-1); |
| } |
| |
| // Free all resources associated with the true. |
| // This means restoring ktrace to its original state. |
| // This assumes tracing has already been stopped. |
| |
| void FreeTrace(const IptConfig& config) { |
| FXL_LOG(INFO) << "FreeTrace called"; |
| |
| fxl::UniqueFD ipt_fd; |
| zx::handle ktrace_handle; |
| if (!OpenDevices(&ipt_fd, nullptr, &ktrace_handle)) |
| return; |
| |
| ssize_t ssize = ioctl_insntrace_free_trace(ipt_fd.get()); |
| if (ssize < 0) { |
| FXL_LOG(ERROR) << "ioctl_insntrace_free_trace failed: " |
| << debugger_utils::ZxErrorString(ssize); |
| } |
| |
| // TODO(dje): Resume original ktracing? Need ability to get old value. |
| // For now set the values to what we need: A later run might still need |
| // the boot time records. |
| zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_STOP, 0, nullptr); |
| #if 0 // TODO(dje): See rewind comments in InitProcessTrace. |
| zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_REWIND, 0, nullptr); |
| #endif |
| zx_ktrace_control(ktrace_handle.get(), KTRACE_ACTION_START, kKtraceGroupMask, |
| nullptr); |
| } |
| |
| } // namespace insntrace |