Skip to content

Commit

Permalink
Kernel: Implement capturing stack trace on a different CPU
Browse files Browse the repository at this point in the history
When trying to get a stack trace of a thread on another CPU we send
a SMP message to that processor to capture the stack trace for us.
  • Loading branch information
tomuta authored and awesomekling committed Nov 11, 2020
1 parent 5b38132 commit 3ee7c21
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 7 deletions.
73 changes: 67 additions & 6 deletions Kernel/Arch/i386/CPU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1279,31 +1279,42 @@ const DescriptorTablePointer& Processor::get_gdtr()
return m_gdtr;
}

bool Processor::get_context_frame_ptr(Thread& thread, u32& frame_ptr, u32& eip)
bool Processor::get_context_frame_ptr(Thread& thread, u32& frame_ptr, u32& eip, bool from_other_processor)
{
bool ret = true;
ScopedCritical critical;
auto& proc = Processor::current();
if (&thread == proc.current_thread()) {
ASSERT(thread.state() == Thread::Running);
asm volatile("movl %%ebp, %%eax"
: "=g"(frame_ptr));
} else {
// If this triggered from another processor, we should never
// hit this code path because the other processor is still holding
// the scheduler lock, which should prevent us from switching
// contexts
ASSERT(!from_other_processor);

// Since the thread may be running on another processor, there
// is a chance a context switch may happen while we're trying
// to get it. It also won't be entirely accurate and merely
// reflect the status at the last context switch.
ScopedSpinLock lock(g_scheduler_lock);
if (thread.state() == Thread::Running) {
ASSERT(thread.cpu() != proc.id());
// TODO: If this is the case, the thread is currently running
// If this is the case, the thread is currently running
// on another processor. We can't trust the kernel stack as
// it may be changing at any time. We need to probably send
// an IPI to that processor, have it walk the stack and wait
// until it returns the data back to us
dbg() << "CPU[" << proc.id() << "] getting stack for "
<< thread << " on other CPU# " << thread.cpu() << " not yet implemented!";
frame_ptr = eip = 0; // TODO
return false;
smp_unicast(thread.cpu(),
[&]() {
dbg() << "CPU[" << Processor::current().id() << "] getting stack for cpu #" << proc.id();
// NOTE: Because we are holding the scheduler lock while
// waiting for this callback to finish, the current thread
// on the target processor cannot change
ret = get_context_frame_ptr(thread, frame_ptr, eip, true);
}, false);
} else {
// We need to retrieve ebp from what was last pushed to the kernel
// stack. Before switching out of that thread, it switch_context
Expand Down Expand Up @@ -1903,6 +1914,56 @@ void Processor::smp_broadcast(void (*callback)(), bool async)
smp_broadcast_message(msg, async);
}

void Processor::smp_unicast_message(u32 cpu, ProcessorMessage& msg, bool async)
{
auto& cur_proc = Processor::current();
ASSERT(cpu != cur_proc.id());
auto& target_proc = processors()[cpu];
msg.async = async;
#ifdef SMP_DEBUG
dbg() << "SMP[" << cur_proc.id() << "]: Send message " << VirtualAddress(&msg) << " to cpu #" << cpu << " proc: " << VirtualAddress(&target_proc);
#endif
atomic_store(&msg.refs, 1u, AK::MemoryOrder::memory_order_release);
if (target_proc->smp_queue_message(msg)) {
APIC::the().send_ipi(cpu);
}

if (!async) {
// If synchronous then we must cleanup and return the message back
// to the pool. Otherwise, the last processor to complete it will return it
while (atomic_load(&msg.refs, AK::MemoryOrder::memory_order_consume) != 0) {
// TODO: pause for a bit?

// We need to check here if another processor may have requested
// us to halt before this message could be delivered. Otherwise
// we're just spinning the CPU because msg.refs will never drop to 0.
if (cur_proc.m_halt_requested.load(AK::MemoryOrder::memory_order_relaxed))
halt_this();
}

smp_cleanup_message(msg);
smp_return_to_pool(msg);
}
}

void Processor::smp_unicast(u32 cpu, void (*callback)(void*), void* data, void (*free_data)(void*), bool async)
{
auto& msg = smp_get_from_pool();
msg.type = ProcessorMessage::CallbackWithData;
msg.callback_with_data.handler = callback;
msg.callback_with_data.data = data;
msg.callback_with_data.free = free_data;
smp_unicast_message(cpu, msg, async);
}

void Processor::smp_unicast(u32 cpu, void (*callback)(), bool async)
{
auto& msg = smp_get_from_pool();
msg.type = ProcessorMessage::CallbackWithData;
msg.callback.handler = callback;
smp_unicast_message(cpu, msg, async);
}

void Processor::smp_broadcast_flush_tlb(VirtualAddress vaddr, size_t page_count)
{
auto& msg = smp_get_from_pool();
Expand Down
20 changes: 19 additions & 1 deletion Kernel/Arch/i386/CPU.h
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,7 @@ class Processor {
static ProcessorMessage& smp_get_from_pool();
static void smp_cleanup_message(ProcessorMessage& msg);
bool smp_queue_message(ProcessorMessage& msg);
static void smp_unicast_message(u32 cpu, ProcessorMessage& msg, bool async);
static void smp_broadcast_message(ProcessorMessage& msg, bool async);
static void smp_broadcast_halt();

Expand Down Expand Up @@ -965,6 +966,23 @@ class Processor {
}
static void smp_broadcast(void (*callback)(), bool async);
static void smp_broadcast(void (*callback)(void*), void* data, void (*free_data)(void*), bool async);
template<typename Callback>
static void smp_unicast(u32 cpu, Callback callback, bool async)
{
auto* data = new Callback(move(callback));
smp_unicast(
cpu,
[](void* data) {
(*reinterpret_cast<Callback*>(data))();
},
data,
[](void* data) {
delete reinterpret_cast<Callback*>(data);
},
async);
}
static void smp_unicast(u32 cpu, void (*callback)(), bool async);
static void smp_unicast(u32 cpu, void (*callback)(void*), void* data, void (*free_data)(void*), bool async);
static void smp_broadcast_flush_tlb(VirtualAddress vaddr, size_t page_count);

template<typename Callback>
Expand Down Expand Up @@ -999,7 +1017,7 @@ class Processor {
void switch_context(Thread*& from_thread, Thread*& to_thread);
[[noreturn]] static void assume_context(Thread& thread, u32 flags);
u32 init_context(Thread& thread, bool leave_crit);
static bool get_context_frame_ptr(Thread& thread, u32& frame_ptr, u32& eip);
static bool get_context_frame_ptr(Thread& thread, u32& frame_ptr, u32& eip, bool = false);

void set_thread_specific(u8* data, size_t len);
};
Expand Down

0 comments on commit 3ee7c21

Please sign in to comment.