1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 04:47:34 +00:00

LibJS: Lazily collect stack trace information

The previous implementation was calling `backtrace()` for every
function call, which is quite slow.

Instead, this implementation provides VM::stack_trace() which unwinds
the native stack, maps it through NativeExecutable::get_source_range
and combines it with source ranges from interpreted call frames.
This commit is contained in:
Simon Wanner 2023-11-01 00:39:28 +01:00 committed by Andreas Kling
parent 77dc7c4d36
commit 68f4d21de2
11 changed files with 96 additions and 46 deletions

View file

@ -15,6 +15,7 @@
#include <LibFileSystem/FileSystem.h>
#include <LibJS/AST.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/JIT/NativeExecutable.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/BoundFunction.h>
@ -1135,4 +1136,66 @@ void VM::pop_execution_context()
on_call_stack_emptied();
}
#if ARCH(X86_64)
struct [[gnu::packed]] NativeStackFrame {
NativeStackFrame* prev;
FlatPtr return_address;
};
#endif
Vector<FlatPtr> VM::get_native_stack_trace() const
{
Vector<FlatPtr> buffer;
#if ARCH(X86_64)
// Manually walk the stack, because backtrace() does not traverse through JIT frames.
auto* frame = bit_cast<NativeStackFrame*>(__builtin_frame_address(0));
while (bit_cast<FlatPtr>(frame) < m_stack_info.top() && bit_cast<FlatPtr>(frame) >= m_stack_info.base()) {
buffer.append(frame->return_address);
frame = frame->prev;
}
#endif
return buffer;
}
static Optional<UnrealizedSourceRange> get_source_range(ExecutionContext const* context, Vector<FlatPtr> const& native_stack)
{
// native function
if (!context->executable)
return {};
auto const* native_executable = context->executable->native_executable();
if (!native_executable) {
// Interpreter frame
if (context->instruction_stream_iterator.has_value())
return context->instruction_stream_iterator->source_range();
return {};
}
// JIT frame
for (auto address : native_stack) {
auto range = native_executable->get_source_range(*context->executable, address);
if (range.has_value()) {
auto realized = range->realize();
return range;
}
}
return {};
}
Vector<StackTraceElement> VM::stack_trace() const
{
auto native_stack = get_native_stack_trace();
Vector<StackTraceElement> stack_trace;
for (ssize_t i = m_execution_context_stack.size() - 1; i >= 0; i--) {
auto* context = m_execution_context_stack[i];
stack_trace.append({
.execution_context = context,
.source_range = get_source_range(context, native_stack).value_or({}),
});
}
return stack_trace;
}
}