X-Git-Url: https://git.saurik.com/apple/system_cmds.git/blobdiff_plain/1a7e3f61d38d679bba59130891c2031b5a0092b6..bd6521f0fc816ab056bc71376f9706a69b3b52c1:/KDBG/TraceFile.hpp diff --git a/KDBG/TraceFile.hpp b/KDBG/TraceFile.hpp new file mode 100644 index 0000000..174493d --- /dev/null +++ b/KDBG/TraceFile.hpp @@ -0,0 +1,257 @@ +// +// TraceFile.hpp +// system_cmds +// +// Created by James McIlree on 4/1/14. +// +// + +#ifndef __system_cmds__TraceFile__ +#define __system_cmds__TraceFile__ + +// These are not (yet) defined in debug.h +// Remove and use kdebug.h ASAP. +#define RAW_VERSION2 0x55aa0200 // RAW_VERSION2 is from Instruments/kperf +#define RAW_VERSION3 0x55aa0300 // RAW_VERSION3 is the new hotness from kperf + +enum class kTraceFileVersion : uint32_t { + V0 = 0, + V1 = 1, + V1Plus = 2, // A 1+ is a 1 with a cpumap + V2 = 3, // Can type 2 contain a cpumap? Looks like no. + V3 = 4, + Unknown = UINT32_MAX +}; + +class TraceFile { + protected: + MappedFile _file; + kTraceFileVersion _version; + bool _is_64_bit; + bool _is_valid; + void* _threadmap; + uint32_t _threadmap_count; + KDCPUMapEntry* _cpumap; + uint32_t _cpumap_count; + void* _events; + uintptr_t _event_count; + std::vector _time_sorted_events; // This is empty unless event sorting is requested. + std::vector _default_cpumap; // If the file does not contain a cpumap, this will be used instead + + template + void sanity_check_event_data(); + + template + void parse(bool, uint32_t, uint32_t); + + public: + TraceFile(const char* path, bool sort_events = false, uint32_t default_ap_count = 24, uint32_t default_iop_count = 0); + + // Returns true if a Machine state can be created. + bool is_valid() const { return _is_valid; } + bool is_64_bit() const { return _is_64_bit; } + kTraceFileVersion version() const { return _version; } + + // Exposed so iOS devices can report over sized trace + bool mmap_failed() const { return _file.mmap_failed(); } + + const KDCPUMapEntry* cpumap() const { return _cpumap; } + uint32_t cpumap_count() const { return _cpumap_count; } + + template + const KDThreadMapEntry* threadmap() const { return reinterpret_cast*>(_threadmap); } + uint32_t threadmap_count() const { return _threadmap_count; } + + template + const KDEvent* events() const { return reinterpret_cast*>(_events); } + uintptr_t event_count() const { return _event_count; } +}; + +// +// This is a very simple attempt to sanity check the event data and prevent +// crashes when reading 32b vs 64b trace data. +// +template +void TraceFile::sanity_check_event_data() { + uintptr_t event_check_count = std::min((uintptr_t)10, _event_count); + + AbsTime last_timestamp; + + for (uintptr_t i=0; i& event = reinterpret_cast*>(_events)[i]; + + if (event.cpu() < 0) { + THROW("Event cpu id is less than 0"); + } + + if (event.cpu() >= _cpumap_count) { + THROW("Event cpu id is greater than the number of configured cpus"); + } + + if (event.timestamp() < last_timestamp) { + THROW("Event Data sanity check found out of order timestamps"); + } + + if (SIZE::is_64_bit) { + if (event.unused() != 0) { + THROW("Event has value set in unknown field"); + } + } + + last_timestamp = event.timestamp(); + } +} + +template +void TraceFile::parse(bool should_presort_events, uint32_t default_ap_count, uint32_t default_iop_count) { + if (TraceDataHeader* header = reinterpret_cast*>(_file.address())) { + KDThreadMapEntry* threadmap = NULL; + uint32_t threadmap_count = 0; + KDCPUMapEntry* cpumap = NULL; + uint32_t cpumap_count = 0; + KDEvent* events = NULL; + kTraceFileVersion version; + + switch (header->version()) { + case RAW_VERSION0: + // Should never happen! + ASSERT(false, "File is RAW_VERSION0"); + THROW("RAW_VERSION0 is ILLEGAL"); + break; + + case RAW_VERSION1: + // Could be either v1 or v1+ + break; + + case RAW_VERSION2: + _version = kTraceFileVersion::V2; + // We do not know how to parse a V2 file + THROW("RAW_VERSION2 is unhandled"); + break; + + case RAW_VERSION3: + _version = kTraceFileVersion::V3; + // We do not know how to parse a V3 file + THROW("RAW_VERSION3 is unhandled"); + break; + + default: + // Could be a v0 + break; + } + + if (header->version() != RAW_VERSION1) { + // If the header is not a RAW_VERSION1, we must assume it is a + // RAW_VERSION0. The difficulty here is that RAW_VERSION0 consists + // of 4 bytes, which are the thread_count. We can't do much + // sanity checking. The first four bytes are already read into + // the existing header, reuse them. We must also reset the file + // offset. + + threadmap_count = header->version(); + threadmap = reinterpret_cast*>(_file.address() + 4); + + // Event data starts immediately following the threadmap + size_t offset = 4 + threadmap_count * sizeof(KDThreadMapEntry); + events = reinterpret_cast*>(_file.address() + offset); + + version = kTraceFileVersion::V0; + } else { + // + // RAW_VERSION1 + // + threadmap_count = header->thread_count(); + threadmap = reinterpret_cast*>(_file.address() + sizeof(TraceDataHeader)); + + size_t threadmap_size_in_bytes = threadmap_count * sizeof(KDThreadMapEntry); + size_t offset_to_event_data = (sizeof(TraceDataHeader) + threadmap_size_in_bytes + 4095) & ~4095; + size_t offset_to_cpumap_data = sizeof(TraceDataHeader) + threadmap_size_in_bytes; + size_t cpumap_bytes = offset_to_event_data - offset_to_cpumap_data; + + // + // In a RAW_VERSION1, there *may* be a cpumap. + // If it exists, it will be between the header and the page aligned offset + // that event data begins at. + // + if (cpumap_bytes > sizeof(kd_cpumap_header) + sizeof(kd_cpumap)) { + kd_cpumap_header* cpumap_header = reinterpret_cast(_file.address() + offset_to_cpumap_data); + if (cpumap_header->version_no == RAW_VERSION1) { + cpumap = (KDCPUMapEntry*)&cpumap_header[1]; + cpumap_count = cpumap_header->cpu_count; + } + } + + // Event data starts at the next PAGE alignment boundary. + // + // Hmm, this could be pretty awful in iOS... + // + // Kernel page size is 4k. Userspace page size is 16kb in 64b. + // Kernel writes the data. Unless the kernel call fails, then userspace writes the data. Blech. + events = reinterpret_cast*>(_file.address() + offset_to_event_data); + } + + uintptr_t event_count = (uintptr_t)_file.size() - (reinterpret_cast(events) - reinterpret_cast(_file.address())); + if (event_count % sizeof(KDEvent) != 0) { + // We're probably looking at the wrong k32/k64. Throw and try the other size. + THROW("Bytes in file does not match an even multiple of Event struct"); + } + event_count /= sizeof(KDEvent); + + if (cpumap == NULL || cpumap_count == 0) { + // No cpumap found, we need to fake one up using the default values. + // + // It would be nice if we could just read the events and derive the + // AP/IOP count, but the IOP events do not have valid tid(s), and + // must be ignored. + + for (uint32_t i=0; i> presorted_events; + if (should_presort_events && event_count) { + _time_sorted_events.reserve(event_count * sizeof(KDEvent)); + memcpy(_time_sorted_events.data(), events, event_count * sizeof(KDEvent)); + events = reinterpret_cast*>(_time_sorted_events.data()); + std::sort(events, events + event_count, [](KDEvent const& p0, KDEvent const& p1) -> bool { + return p0.timestamp() < p1.timestamp(); + }); + } + + _threadmap = threadmap; + _threadmap_count = threadmap_count; + + _cpumap = cpumap; + _cpumap_count = cpumap_count; + + _events = events; + _event_count = event_count; + + _version = version; + _is_64_bit = SIZE::is_64_bit; + + sanity_check_event_data(); + + // + // Okay, success if we made it this far. + // + _is_valid = true; + } +} + +#endif /* defined(__system_cmds__TraceFile__) */