Last active
September 10, 2024 19:06
-
-
Save bnoordhuis/5192966 to your computer and use it in GitHub Desktop.
Decode C/C++ and V8 JS stack frames.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "v8.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <assert.h> | |
#include <cxxabi.h> | |
#include <dlfcn.h> | |
using namespace v8; | |
static Handle<Value> Enter(const Arguments& args); | |
static Handle<Value> Trace(const Arguments& args); | |
static void backtrace(); | |
static void** stack_top; | |
int main(int argc, char** argv) | |
{ | |
void* stack_canary; | |
stack_top = &stack_canary; | |
V8::SetFlagsFromCommandLine(&argc, argv, true); | |
HandleScope handle_scope; | |
Persistent<Context> context = Context::New(); | |
Context::Scope context_scope(context); | |
Local<Object> global = context->Global(); | |
global->Set(String::New("enter"), | |
FunctionTemplate::New(Enter)->GetFunction()); | |
global->Set(String::New("trace"), | |
FunctionTemplate::New(Trace)->GetFunction()); | |
Local<String> script_source = String::New( | |
"enter(next); \n" | |
"function next() { \n" | |
" hop(); \n" | |
"} \n" | |
"function hop() { \n" | |
" trace(); \n" | |
"} \n"); | |
Local<String> script_name = String::New("<internal>"); | |
Local<Script> script = Script::New(script_source, script_name); | |
assert(script.IsEmpty() == false); | |
Local<Value> rc = script->Run(); | |
assert(rc.IsEmpty() == false); | |
} | |
static Handle<Value> Enter(const Arguments& args) | |
{ | |
assert(args[0]->IsFunction()); | |
HandleScope handle_scope; | |
Local<Object> global = Context::GetCurrent()->Global(); | |
Local<Function> fun = args[0].As<Function>(); | |
Local<Value> rc = fun->Call(global, 0, NULL); | |
assert(rc.IsEmpty() == false); | |
return handle_scope.Close(rc); | |
} | |
static Handle<Value> Trace(const Arguments&) | |
{ | |
backtrace(); | |
return Undefined(); | |
} | |
#define OFFSET(base, addr) \ | |
(static_cast<long>(static_cast<const char*>(addr) - \ | |
static_cast<const char*>(base))) | |
struct Frame | |
{ | |
const Frame* frame_pointer; | |
const void* return_address; | |
}; | |
// Linked list. Wildly inefficient. | |
struct Code | |
{ | |
Code* next; | |
const void* start; | |
const void* end; | |
char name[1]; | |
}; | |
static int stack_trace_index; | |
static Local<StackTrace> stack_trace; | |
static struct Code* code_head; | |
static void add_code(const char* name, | |
unsigned int namelen, | |
const void* start, | |
const void* end) | |
{ | |
Code* code = static_cast<Code*>(malloc(sizeof(*code) + namelen)); | |
if (code == NULL) return; | |
memcpy(code->name, name, namelen); | |
code->name[namelen] = '\0'; | |
code->start = start; | |
code->end = end; | |
code->next = code_head; | |
code_head = code; | |
} | |
static void free_code() | |
{ | |
while (code_head != NULL) { | |
Code* code = code_head; | |
code_head = code->next; | |
free(code); | |
} | |
} | |
static Code* find_code(const void* addr) | |
{ | |
for (Code* code = code_head; code != NULL; code = code->next) | |
if (code->start <= addr && code->end >= addr) | |
return code; | |
return NULL; | |
} | |
static void jit_code_event(const JitCodeEvent* ev) | |
{ | |
if (ev->type == JitCodeEvent::CODE_ADDED) { | |
add_code(ev->name.str, | |
ev->name.len, | |
ev->code_start, | |
static_cast<const char*>(ev->code_start) + ev->code_len); | |
} | |
} | |
static bool print_c_frame(const Frame* frame, FILE* stream) | |
{ | |
Dl_info info; | |
if (dladdr(frame->return_address, &info) == 0) | |
return false; | |
const char* name = info.dli_sname; | |
const char* demangled_name = abi::__cxa_demangle(name, NULL, NULL, NULL); | |
if (demangled_name != NULL) | |
name = demangled_name; | |
fprintf(stream, | |
"%lx+%lx %s %s(%p)\n", | |
reinterpret_cast<long>(info.dli_saddr), | |
OFFSET(info.dli_saddr, frame->return_address), | |
name, | |
info.dli_fname, | |
info.dli_fbase); | |
if (name == demangled_name) | |
free(const_cast<char*>(name)); | |
return true; | |
} | |
static bool print_js_frame(const Frame* frame, FILE* stream) | |
{ | |
if (code_head == NULL) { | |
// Lazy init. | |
V8::SetJitCodeEventHandler(kJitCodeEventEnumExisting, jit_code_event); | |
V8::SetJitCodeEventHandler(kJitCodeEventDefault, NULL); | |
stack_trace = StackTrace::CurrentStackTrace(64); | |
stack_trace_index = 0; | |
} | |
Code* code = find_code(frame->return_address); | |
if (code == NULL) | |
return false; | |
if (stack_trace_index < stack_trace->GetFrameCount()) { | |
Local<StackFrame> js_frame = stack_trace->GetFrame(stack_trace_index++); | |
String::Utf8Value function_name(js_frame->GetFunctionName()); | |
if (function_name.length() > 0) { | |
String::Utf8Value script_name(js_frame->GetScriptName()); | |
fprintf(stream, | |
"%lx+%lx %s %s:%d:%d\n", | |
reinterpret_cast<long>(code->start), | |
OFFSET(code->start, frame->return_address), | |
*function_name, | |
*script_name, | |
js_frame->GetLineNumber(), | |
js_frame->GetColumn()); | |
return true; | |
} | |
} | |
fprintf(stream, | |
"%lx+%lx %s\n", | |
reinterpret_cast<long>(code->start), | |
OFFSET(code->start, frame->return_address), | |
code->name); | |
return true; | |
} | |
static void print_frame(const Frame* frame, FILE* stream) | |
{ | |
if (print_c_frame(frame, stream)) | |
return; | |
if (print_js_frame(frame, stream)) | |
return; | |
// Unresolved. Just print the raw address. | |
fprintf(stream, "%lx\n", reinterpret_cast<long>(frame->return_address)); | |
} | |
__attribute__((noinline)) | |
void backtrace(void) | |
{ | |
HandleScope handle_scope; | |
const Frame* frame; | |
__asm__ __volatile__ ("mov %%rbp, %0" : "=g" (frame)); | |
while (frame < reinterpret_cast<Frame*>(stack_top)) { | |
print_frame(frame, stderr); | |
frame = frame->frame_pointer; | |
} | |
free_code(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment