X-Git-Url: https://git.saurik.com/apple/system_cmds.git/blobdiff_plain/1a7e3f61d38d679bba59130891c2031b5a0092b6..bd6521f0fc816ab056bc71376f9706a69b3b52c1:/KDBG/MachineCPU.mutable-impl.hpp?ds=inline diff --git a/KDBG/MachineCPU.mutable-impl.hpp b/KDBG/MachineCPU.mutable-impl.hpp new file mode 100644 index 0000000..ceb25e1 --- /dev/null +++ b/KDBG/MachineCPU.mutable-impl.hpp @@ -0,0 +1,329 @@ +// +// MachineCPU.mutable-impl.hpp +// KDBG +// +// Created by James McIlree on 11/7/12. +// Copyright (c) 2014 Apple. All rights reserved. +// + +template +void MachineCPU::set_idle(AbsTime timestamp) { + ASSERT(is_idle_state_initialized(), "Setting idle before state was initialized"); + ASSERT(!is_intr(), "Setting idle while in interrupt"); + ASSERT(!is_idle(), "Setting idle while already idle"); + ASSERT(_begin_idle == 0, "Sanity"); + + _begin_idle = timestamp; + _flags |= (uint32_t)kMachineCPUFlag::IsStateIdle; +} + +template +void MachineCPU::clear_idle(AbsTime timestamp) { + ASSERT(is_idle_state_initialized(), "Clearing idle before state was initialized"); + ASSERT(!is_intr(), "Clearing idle while in interrupt"); + ASSERT(is_idle(), "Clearing idle while not idle"); + + _cpu_idle.emplace_back(_begin_idle, timestamp - _begin_idle); + DEBUG_ONLY(_begin_idle = AbsTime(0);) + _flags &= ~(uint32_t)kMachineCPUFlag::IsStateIdle; +} + +template +void MachineCPU::set_deactivate_switch_to_idle_thread() { + ASSERT(!is_deactivate_switch_to_idle_thread(), "State already set"); + ASSERT(!is_intr(), "This state should not occur during INTR"); + + _flags |= (uint32_t)kMachineCPUFlag::IsStateDeactivatedForcedSwitchToIdleThread; +} + +template +void MachineCPU::clear_deactivate_switch_to_idle_thread() { + ASSERT(is_deactivate_switch_to_idle_thread(), "Clearing state when not set"); + ASSERT(!is_intr(), "This state transition should not occur during INTR"); + + _flags &= ~(uint32_t)kMachineCPUFlag::IsStateDeactivatedForcedSwitchToIdleThread; +} + +template +void MachineCPU::initialize_idle_state(bool is_idle, AbsTime timestamp) { + ASSERT(!is_idle_state_initialized(), "Attempt to initialize Idle state more than once"); + ASSERT(!this->is_idle(), "Attempt to initialize Idle state while already idle"); + + if (is_idle) { + _begin_idle = timestamp; + _flags |= (uint32_t)kMachineCPUFlag::IsStateIdle; + } + + _flags |= (uint32_t)kMachineCPUFlag::IsStateIdleInitialized; +} + +template +void MachineCPU::set_intr(AbsTime timestamp) { + // We can take an INTR in state Unknown, IDLE, and RUNNING. + ASSERT(is_intr_state_initialized(), "Setting INTR before state was initialized"); + ASSERT(!is_intr(), "Setting INTR when already in state INTR"); + ASSERT(_begin_intr == 0, "Sanity"); + + _begin_intr = timestamp; + _flags |= (uint32_t)kMachineCPUFlag::IsStateINTR; +} + +template +void MachineCPU::clear_intr(AbsTime timestamp) { + ASSERT(is_intr_state_initialized(), "Clearing INTR before state was initialized"); + ASSERT(is_intr(), "Clearing INTR when not in INTR"); + + _cpu_intr.emplace_back(_begin_intr, timestamp - _begin_intr); + DEBUG_ONLY(_begin_intr = AbsTime(0);) + _flags &= ~(uint32_t)kMachineCPUFlag::IsStateINTR; +} + +template +void MachineCPU::initialize_intr_state(bool is_intr, AbsTime timestamp) { + ASSERT(!is_intr_state_initialized(), "Attempt to initialize INTR state more than once"); + ASSERT(!this->is_intr(), "Attempt to initialize INTR state while already INTR"); + + if (is_intr) { + _begin_intr = timestamp; + _flags |= (uint32_t)kMachineCPUFlag::IsStateINTR; + } + + _flags |= (uint32_t)kMachineCPUFlag::IsStateINTRInitialized; +} + +template +void MachineCPU::initialize_thread_state(MachineThread* init_thread, AbsTime timestamp) { + ASSERT(!is_thread_state_initialized(), "Attempt to initialize thread state more than once"); + ASSERT(!_thread, "Sanity"); + + // When initializing the thread state, the TID lookup may fail. This + // can happen if there wasn't a threadmap, or if the thread was created + // later in the trace. We explicitly allow NULL as a valid value here. + // NULL means "Go ahead and set the init flag, but we will not emit a + // runq event later when a real context switch happens + + _flags |= (uint32_t)kMachineCPUFlag::IsStateThreadInitialized; + if (init_thread) { + _cpu_runq.emplace_back(init_thread, true, timestamp); + _thread = init_thread; + } +} + +template +void MachineCPU::context_switch(MachineThread* to_thread, MachineThread* from_thread, AbsTime timestamp) { + // + // We cannot context switch in INTR or Idle + // + // The one exception is if we were thread_initialized with NULL, + // then the first context switch will happen at idle. + ASSERT(!is_intr(), "May not context switch while in interrupt"); + ASSERT(!is_idle() || _thread == NULL && is_thread_state_initialized(), "May not context switch while idle"); + ASSERT(to_thread, "May not context switch to NULL"); + + // The threads should match, unless... + // 1) We're uninitialized; we don't know who was on cpu + // 2) VERY RARE: A process EXEC'd, and we made a new thread for the new process. The tid's will still match, and the old thread should be marked as trace terminated. + ASSERT(from_thread == _thread || _thread == NULL || (_thread->is_trace_terminated() && _thread->tid() == from_thread->tid()), "From thread does not match thread on cpu"); + + // Very rarely, we init a cpu to a thread, and then event[0] is a mach_sched + // or other context switch event. If that has happened, just discard the init + // thread entry. + if (_cpu_runq.size() == 1) { + if (_cpu_runq.back().is_event_zero_init_thread()) { + if (timestamp == _cpu_runq.back().timestamp()) { + _cpu_runq.pop_back(); + } + } + } + + ASSERT(_cpu_runq.empty() || timestamp > _cpu_runq.back().timestamp(), "Out of order timestamps"); + ASSERT(_cpu_runq.size() < 2 || !_cpu_runq.back().is_event_zero_init_thread(), "Sanity"); + + _cpu_runq.emplace_back(to_thread, false, timestamp); + _thread = to_thread; +} + +template +void MachineCPU::post_initialize(AbsInterval events_timespan) { +#if !defined(NDEBUG) && !defined(NS_BLOCK_ASSERTIONS) + // Make sure everything is sorted + if (_cpu_runq.size() > 1) { + for (uint32_t i=1; i<_cpu_runq.size(); ++i) { + ASSERT(_cpu_runq[i-1].timestamp() < _cpu_runq[i].timestamp(), "Out of order run events"); + } + } + if (_cpu_idle.size() > 1) { + for (uint32_t i=1; i<_cpu_idle.size(); ++i) { + ASSERT(_cpu_idle[i-1].max() < _cpu_idle[i].location(), "Out of order idle events"); + } + } + if (_cpu_intr.size() > 1) { + for (uint32_t i=1; i<_cpu_intr.size(); ++i) { + ASSERT(_cpu_intr[i-1].max() < _cpu_intr[i].location(), "Out of order intr events"); + } + } +#endif + + // We do not need to flush the current thread on cpu, as the cpu + // runq only records "on" events, and assumes a duration of "until + // the next thread arrives or end of time" + + + // if we have a pending intr state, flush it. + // We want to flush the intr first, so an idle + // flush doesn't assert. + if (is_intr()) + clear_intr(events_timespan.max()); + + // If we have a pending idle state, flush it. + if (is_idle()) + clear_idle(events_timespan.max()); + + if (!_cpu_runq.empty() || !_cpu_idle.empty() || !_cpu_intr.empty()) { + // + // Collapse all the events into a single timeline + // + + // Check this math once we're done building the timeline. + size_t guessed_capacity = _cpu_runq.size() + _cpu_idle.size() * 2 + _cpu_intr.size() * 2; + _timeline.reserve(guessed_capacity); + + auto runq_it = _cpu_runq.begin(); + auto idle_it = _cpu_idle.begin(); + auto intr_it = _cpu_intr.begin(); + + // Starting these at 0 will for an update to valid values in + // the first pass of the workloop. + + AbsInterval current_runq(AbsTime(0), AbsTime(0)); + AbsInterval current_idle(AbsTime(0), AbsTime(0)); + AbsInterval current_intr(AbsTime(0), AbsTime(0)); + + MachineThread* current_thread = NULL; + + AbsTime cursor(events_timespan.location()); + while (events_timespan.contains(cursor)) { + // + // First we see if anyone needs updating with the next component. + // + if (cursor >= current_runq.max()) { + if (runq_it != _cpu_runq.end()) { + AbsTime end, begin = runq_it->timestamp(); + if (runq_it+1 != _cpu_runq.end()) + end = (runq_it+1)->timestamp(); + else + end = events_timespan.max(); + + current_runq = AbsInterval(begin, end - begin); + current_thread = runq_it->thread(); + ++runq_it; + } else { + // This will force future update checks to always fail. + current_runq = AbsInterval(events_timespan.max() + AbsTime(1), AbsTime(1)); + current_thread = NULL; + } + } + + if (cursor >= current_idle.max()) { + if (idle_it != _cpu_idle.end()) { + current_idle = *idle_it; + ++idle_it; + } else { + // This will force future update checks to always fail. + current_idle = AbsInterval(events_timespan.max() + AbsTime(1), AbsTime(1)); + } + } + + if (cursor >= current_intr.max()) { + if (intr_it != _cpu_intr.end()) { + current_intr = *intr_it; + ++intr_it; + } else { + // This will force future update checks to always fail. + current_intr = AbsInterval(events_timespan.max() + AbsTime(1), AbsTime(1)); + } + } + + // + // Now we see what type of activity we will be recording. + // + // This is heirarchical, intr > idle > run > unknown. + // + + kCPUActivity type = kCPUActivity::Unknown; + + if (current_runq.contains(cursor)) + type = kCPUActivity::Run; + + if (current_idle.contains(cursor)) + type = kCPUActivity::Idle; + + if (current_intr.contains(cursor)) + type = kCPUActivity::INTR; + + // + // Now we know the type, and the starting location. + // We must find the end. + // + // Since this is heirarchical, each type may end on + // its own "end", or the "begin" of a type higher than + // itself. An idle can end at its end, or at an intr begin. + // + + AbsTime end; + switch (type) { + case kCPUActivity::Unknown: + end = std::min({ events_timespan.max(), current_runq.location(), current_idle.location(), current_intr.location() }); + break; + + case kCPUActivity::Run: + end = std::min({ current_runq.max(), current_idle.location(), current_intr.location() }); + break; + + case kCPUActivity::Idle: + end = std::min(current_idle.max(), current_intr.location()); + break; + + case kCPUActivity::INTR: + end = current_intr.max(); + break; + } + + // + // Now we drop in the new activity + // + if (type == kCPUActivity::Run) { + ASSERT(current_thread, "Current thread is NULL"); + // Its a context switch if we are at the beginning of the runq interval + _timeline.emplace_back(current_thread, AbsInterval(cursor, end - cursor), current_runq.location() == cursor); + } else + _timeline.emplace_back(type, AbsInterval(cursor, end - cursor)); + + // + // And bump the cursor to the end... + // + cursor = end; + } + +#if !defined(NDEBUG) && !defined(NS_BLOCK_ASSERTIONS) + for (auto it = _timeline.begin(); it != _timeline.end(); ++it) { + auto next_it = it + 1; + ASSERT(events_timespan.contains(*it), "activity not contained in events_timespan"); + if (next_it != _timeline.end()) { + ASSERT(it->max() == next_it->location(), "activity not end to end"); + bool initial_idle_state = ((it == _timeline.begin()) && it->is_idle()); + ASSERT(!next_it->is_context_switch() || (it->is_run() || it->is_unknown() || initial_idle_state) , "Context switch activity preceeded by !run activity"); + } + } +#endif + } + + _cpu_runq.clear(); + _cpu_runq.shrink_to_fit(); + + _cpu_idle.clear(); + _cpu_idle.shrink_to_fit(); + + _cpu_intr.clear(); + _cpu_intr.shrink_to_fit(); +}