mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-22 09:21:57 -05:00
Kernel: Share Processor::capture_stack_trace() between architectures
This makes Processor::capture_stack_trace() work on all our architectures. For this function to work on AArch64 and RISC-V, the frame pointer has to be saved during context switches. AArch64 and RISC-V don't support SMP yet, so the code for getting a backtrace for processes running on other cores is guarded behind a '#if ARCH(X86_64)'.
This commit is contained in:
parent
fa39086922
commit
e7b8eed005
5 changed files with 141 additions and 140 deletions
|
@ -4,9 +4,12 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StackUnwinder.h>
|
||||
#include <Kernel/Arch/Processor.h>
|
||||
#include <Kernel/Arch/TrapFrame.h>
|
||||
#include <Kernel/Interrupts/InterruptDisabler.h>
|
||||
#include <Kernel/Library/StdLib.h>
|
||||
#include <Kernel/Memory/ScopedAddressSpaceSwitcher.h>
|
||||
#include <Kernel/Sections.h>
|
||||
#include <Kernel/Tasks/Scheduler.h>
|
||||
#include <Kernel/Tasks/Thread.h>
|
||||
|
@ -132,4 +135,135 @@ void do_context_first_init(Thread* from_thread, Thread* to_thread)
|
|||
Scheduler::leave_on_first_switch(InterruptsState::Disabled);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<Vector<FlatPtr, 32>> ProcessorBase<T>::capture_stack_trace(Thread& thread, size_t max_frames)
|
||||
{
|
||||
FlatPtr frame_ptr = 0, pc = 0;
|
||||
Vector<FlatPtr, 32> stack_trace;
|
||||
|
||||
auto walk_stack = [&](FlatPtr frame_ptr) -> ErrorOr<void> {
|
||||
constexpr size_t max_stack_frames = 4096;
|
||||
bool is_walking_userspace_stack = false;
|
||||
TRY(stack_trace.try_append(pc));
|
||||
|
||||
TRY(AK::unwind_stack_from_frame_pointer(
|
||||
frame_ptr,
|
||||
[&is_walking_userspace_stack](FlatPtr address) -> ErrorOr<FlatPtr> {
|
||||
if (!Memory::is_user_address(VirtualAddress { address })) {
|
||||
if (is_walking_userspace_stack) {
|
||||
dbgln("SHENANIGANS! Userspace stack points back into kernel memory");
|
||||
return EFAULT;
|
||||
}
|
||||
} else {
|
||||
is_walking_userspace_stack = true;
|
||||
}
|
||||
|
||||
FlatPtr value;
|
||||
|
||||
if (Memory::is_user_range(VirtualAddress { address }, sizeof(FlatPtr))) {
|
||||
TRY(copy_from_user(&value, bit_cast<FlatPtr*>(address)));
|
||||
} else {
|
||||
void* fault_at;
|
||||
if (!safe_memcpy(&value, bit_cast<FlatPtr*>(address), sizeof(FlatPtr), fault_at))
|
||||
return EFAULT;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
[&stack_trace, max_frames](AK::StackFrame stack_frame) -> ErrorOr<IterationDecision> {
|
||||
if (stack_trace.size() >= max_stack_frames || (max_frames != 0 && stack_trace.size() >= max_frames))
|
||||
return IterationDecision::Break;
|
||||
|
||||
TRY(stack_trace.try_append(stack_frame.return_address));
|
||||
|
||||
return IterationDecision::Continue;
|
||||
}));
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
auto capture_current_thread = [&]() {
|
||||
frame_ptr = bit_cast<FlatPtr>(__builtin_frame_address(0));
|
||||
pc = bit_cast<FlatPtr>(__builtin_return_address(0));
|
||||
|
||||
return walk_stack(frame_ptr);
|
||||
};
|
||||
|
||||
// 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.
|
||||
SpinlockLocker lock(g_scheduler_lock);
|
||||
if (&thread == Processor::current_thread()) {
|
||||
VERIFY(thread.state() == Thread::State::Running);
|
||||
// Leave the scheduler lock. If we trigger page faults we may
|
||||
// need to be preempted. Since this is our own thread it won't
|
||||
// cause any problems as the stack won't change below this frame.
|
||||
lock.unlock();
|
||||
TRY(capture_current_thread());
|
||||
} else if (thread.is_active()) {
|
||||
#if ARCH(X86_64)
|
||||
VERIFY(thread.cpu() != Processor::current_id());
|
||||
// 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
|
||||
auto& proc = Processor::current();
|
||||
ErrorOr<void> result;
|
||||
Processor::smp_unicast(
|
||||
thread.cpu(),
|
||||
[&]() {
|
||||
dbgln("CPU[{}] getting stack for cpu #{}", Processor::current_id(), proc.id());
|
||||
ScopedAddressSpaceSwitcher switcher(thread.process());
|
||||
VERIFY(&Processor::current() != &proc);
|
||||
VERIFY(&thread == Processor::current_thread());
|
||||
// NOTE: Because the other processor is still holding the
|
||||
// scheduler lock while waiting for this callback to finish,
|
||||
// the current thread on the target processor cannot change
|
||||
|
||||
// TODO: What to do about page faults here? We might deadlock
|
||||
// because the other processor is still holding the
|
||||
// scheduler lock...
|
||||
result = capture_current_thread();
|
||||
},
|
||||
false);
|
||||
TRY(result);
|
||||
#elif ARCH(AARCH64) || ARCH(RISCV64)
|
||||
VERIFY_NOT_REACHED(); // We don't support SMP on AArch64 and RISC-V yet, so this should be unreachable.
|
||||
#else
|
||||
# error Unknown architecture
|
||||
#endif
|
||||
} else {
|
||||
switch (thread.state()) {
|
||||
case Thread::State::Running:
|
||||
VERIFY_NOT_REACHED(); // should have been handled above
|
||||
case Thread::State::Runnable:
|
||||
case Thread::State::Stopped:
|
||||
case Thread::State::Blocked:
|
||||
case Thread::State::Dying:
|
||||
case Thread::State::Dead: {
|
||||
ScopedAddressSpaceSwitcher switcher(thread.process());
|
||||
auto& regs = thread.regs();
|
||||
|
||||
pc = regs.ip();
|
||||
frame_ptr = regs.frame_pointer();
|
||||
|
||||
// TODO: We need to leave the scheduler lock here, but we also
|
||||
// need to prevent the target thread from being run while
|
||||
// we walk the stack
|
||||
lock.unlock();
|
||||
TRY(walk_stack(frame_ptr));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
dbgln("Cannot capture stack trace for thread {} in state {}", thread, thread.state_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stack_trace;
|
||||
}
|
||||
template ErrorOr<Vector<FlatPtr, 32>> ProcessorBase<Processor>::capture_stack_trace(Thread&, size_t);
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,5 @@ template void ProcessorBase<Processor>::initialize_context_switching(Thread& ini
|
|||
template void ProcessorBase<Processor>::switch_context(Thread*& from_thread, Thread*& to_thread);
|
||||
template void ProcessorBase<Processor>::assume_context(Thread& thread, InterruptsState new_interrupts_state);
|
||||
template FlatPtr ProcessorBase<Processor>::init_context(Thread& thread, bool leave_crit);
|
||||
template ErrorOr<Vector<FlatPtr, 32>> ProcessorBase<Processor>::capture_stack_trace(Thread& thread, size_t max_frames);
|
||||
template u32 ProcessorBase<Processor>::smp_wake_n_idle_processors(u32 wake_count);
|
||||
}
|
||||
|
|
|
@ -165,6 +165,7 @@ void ProcessorBase<T>::switch_context(Thread*& from_thread, Thread*& to_thread)
|
|||
"str x30, [sp, #(30 * 8)] \n"
|
||||
"mov x0, sp \n"
|
||||
"str x0, %[from_sp] \n"
|
||||
"str fp, %[from_fp] \n"
|
||||
"ldr x0, =1f \n"
|
||||
"str x0, %[from_ip] \n"
|
||||
|
||||
|
@ -213,6 +214,7 @@ void ProcessorBase<T>::switch_context(Thread*& from_thread, Thread*& to_thread)
|
|||
:
|
||||
[from_ip] "=m"(from_thread->regs().elr_el1),
|
||||
[from_sp] "=m"(from_thread->regs().sp_el0),
|
||||
[from_fp] "=m"(from_thread->regs().x[29]),
|
||||
"=m"(from_thread),
|
||||
"=m"(to_thread)
|
||||
|
||||
|
@ -364,15 +366,6 @@ void ProcessorBase<T>::exit_trap(TrapFrame& trap)
|
|||
check_invoke_scheduler();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<Vector<FlatPtr, 32>> ProcessorBase<T>::capture_stack_trace(Thread& thread, size_t max_frames)
|
||||
{
|
||||
(void)thread;
|
||||
(void)max_frames;
|
||||
dbgln("FIXME: Implement Processor::capture_stack_trace() for AArch64");
|
||||
return Vector<FlatPtr, 32> {};
|
||||
}
|
||||
|
||||
NAKED void thread_context_first_enter(void)
|
||||
{
|
||||
asm(
|
||||
|
|
|
@ -258,6 +258,10 @@ void ProcessorBase<T>::switch_context(Thread*& from_thread, Thread*& to_thread)
|
|||
// Store current sp as from_thread's sp.
|
||||
"sd sp, %[from_sp] \n"
|
||||
|
||||
// Store current fp as from_thread's sp.
|
||||
// This is needed to make capture_stack_trace() work.
|
||||
"sd fp, %[from_fp] \n"
|
||||
|
||||
// Set from_thread's pc to label "1"
|
||||
"la t0, 1f \n"
|
||||
"sd t0, %[from_ip] \n"
|
||||
|
@ -327,6 +331,7 @@ void ProcessorBase<T>::switch_context(Thread*& from_thread, Thread*& to_thread)
|
|||
:
|
||||
[from_ip] "=m"(from_thread->regs().pc),
|
||||
[from_sp] "=m"(from_thread->regs().x[1]),
|
||||
[from_fp] "=m"(from_thread->regs().x[7]),
|
||||
"=m"(from_thread),
|
||||
"=m"(to_thread)
|
||||
|
||||
|
@ -473,13 +478,6 @@ void ProcessorBase<T>::exit_trap(TrapFrame& trap)
|
|||
check_invoke_scheduler();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<Vector<FlatPtr, 32>> ProcessorBase<T>::capture_stack_trace(Thread&, size_t)
|
||||
{
|
||||
dbgln("FIXME: Implement Processor::capture_stack_trace() for riscv64");
|
||||
return Vector<FlatPtr, 32> {};
|
||||
}
|
||||
|
||||
extern "C" void context_first_init(Thread* from_thread, Thread* to_thread);
|
||||
extern "C" void context_first_init(Thread* from_thread, Thread* to_thread)
|
||||
{
|
||||
|
|
|
@ -758,129 +758,6 @@ DescriptorTablePointer const& Processor::get_gdtr()
|
|||
return m_gdtr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<Vector<FlatPtr, 32>> ProcessorBase<T>::capture_stack_trace(Thread& thread, size_t max_frames)
|
||||
{
|
||||
FlatPtr frame_ptr = 0, ip = 0;
|
||||
Vector<FlatPtr, 32> stack_trace;
|
||||
|
||||
auto walk_stack = [&](FlatPtr frame_ptr) -> ErrorOr<void> {
|
||||
constexpr size_t max_stack_frames = 4096;
|
||||
bool is_walking_userspace_stack = false;
|
||||
TRY(stack_trace.try_append(ip));
|
||||
|
||||
TRY(AK::unwind_stack_from_frame_pointer(
|
||||
frame_ptr,
|
||||
[&is_walking_userspace_stack](FlatPtr address) -> ErrorOr<FlatPtr> {
|
||||
if (!Memory::is_user_address(VirtualAddress { address })) {
|
||||
if (is_walking_userspace_stack) {
|
||||
dbgln("SHENANIGANS! Userspace stack points back into kernel memory");
|
||||
return EFAULT;
|
||||
}
|
||||
} else {
|
||||
is_walking_userspace_stack = true;
|
||||
}
|
||||
|
||||
FlatPtr value;
|
||||
|
||||
if (Memory::is_user_range(VirtualAddress { address }, sizeof(FlatPtr))) {
|
||||
TRY(copy_from_user(&value, bit_cast<FlatPtr*>(address)));
|
||||
} else {
|
||||
void* fault_at;
|
||||
if (!safe_memcpy(&value, bit_cast<FlatPtr*>(address), sizeof(FlatPtr), fault_at))
|
||||
return EFAULT;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
[&stack_trace, max_frames](AK::StackFrame stack_frame) -> ErrorOr<IterationDecision> {
|
||||
if (stack_trace.size() >= max_stack_frames || (max_frames != 0 && stack_trace.size() >= max_frames))
|
||||
return IterationDecision::Break;
|
||||
|
||||
TRY(stack_trace.try_append(stack_frame.return_address));
|
||||
|
||||
return IterationDecision::Continue;
|
||||
}));
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
auto capture_current_thread = [&]() {
|
||||
frame_ptr = (FlatPtr)__builtin_frame_address(0);
|
||||
ip = (FlatPtr)__builtin_return_address(0);
|
||||
|
||||
return walk_stack(frame_ptr);
|
||||
};
|
||||
|
||||
// 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.
|
||||
SpinlockLocker lock(g_scheduler_lock);
|
||||
if (&thread == Processor::current_thread()) {
|
||||
VERIFY(thread.state() == Thread::State::Running);
|
||||
// Leave the scheduler lock. If we trigger page faults we may
|
||||
// need to be preempted. Since this is our own thread it won't
|
||||
// cause any problems as the stack won't change below this frame.
|
||||
lock.unlock();
|
||||
TRY(capture_current_thread());
|
||||
} else if (thread.is_active()) {
|
||||
VERIFY(thread.cpu() != Processor::current_id());
|
||||
// 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
|
||||
auto& proc = Processor::current();
|
||||
ErrorOr<void> result;
|
||||
Processor::smp_unicast(
|
||||
thread.cpu(),
|
||||
[&]() {
|
||||
dbgln("CPU[{}] getting stack for cpu #{}", Processor::current_id(), proc.id());
|
||||
ScopedAddressSpaceSwitcher switcher(thread.process());
|
||||
VERIFY(&Processor::current() != &proc);
|
||||
VERIFY(&thread == Processor::current_thread());
|
||||
// NOTE: Because the other processor is still holding the
|
||||
// scheduler lock while waiting for this callback to finish,
|
||||
// the current thread on the target processor cannot change
|
||||
|
||||
// TODO: What to do about page faults here? We might deadlock
|
||||
// because the other processor is still holding the
|
||||
// scheduler lock...
|
||||
result = capture_current_thread();
|
||||
},
|
||||
false);
|
||||
TRY(result);
|
||||
} else {
|
||||
switch (thread.state()) {
|
||||
case Thread::State::Running:
|
||||
VERIFY_NOT_REACHED(); // should have been handled above
|
||||
case Thread::State::Runnable:
|
||||
case Thread::State::Stopped:
|
||||
case Thread::State::Blocked:
|
||||
case Thread::State::Dying:
|
||||
case Thread::State::Dead: {
|
||||
ScopedAddressSpaceSwitcher switcher(thread.process());
|
||||
auto& regs = thread.regs();
|
||||
|
||||
ip = regs.ip();
|
||||
frame_ptr = regs.rbp;
|
||||
|
||||
// TODO: We need to leave the scheduler lock here, but we also
|
||||
// need to prevent the target thread from being run while
|
||||
// we walk the stack
|
||||
lock.unlock();
|
||||
TRY(walk_stack(frame_ptr));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
dbgln("Cannot capture stack trace for thread {} in state {}", thread, thread.state_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stack_trace;
|
||||
}
|
||||
|
||||
ProcessorContainer& Processor::processors()
|
||||
{
|
||||
return s_processors;
|
||||
|
|
Loading…
Reference in a new issue