blob: b171339c24da6d9d83c83b7ad3270fef9ec25dbc [file] [log] [blame]
// Copyright 2006-2009 the V8 project authors. All rights reserved.
//
// Tests of profiler-related functions from log.h
#ifdef ENABLE_LOGGING_AND_PROFILING
#include <stdlib.h>
#include "v8.h"
#include "log.h"
#include "top.h"
#include "cctest.h"
using v8::Function;
using v8::Local;
using v8::Object;
using v8::Script;
using v8::String;
using v8::Value;
using v8::internal::byte;
using v8::internal::Handle;
using v8::internal::JSFunction;
using v8::internal::StackTracer;
using v8::internal::TickSample;
using v8::internal::Top;
static v8::Persistent<v8::Context> env;
static struct {
StackTracer* tracer;
TickSample* sample;
} trace_env = { NULL, NULL };
static void InitTraceEnv(StackTracer* tracer, TickSample* sample) {
trace_env.tracer = tracer;
trace_env.sample = sample;
}
static void DoTrace(unsigned int fp) {
trace_env.sample->fp = fp;
// sp is only used to define stack high bound
trace_env.sample->sp =
reinterpret_cast<unsigned int>(trace_env.sample) - 10240;
trace_env.tracer->Trace(trace_env.sample);
}
// Hide c_entry_fp to emulate situation when sampling is done while
// pure JS code is being executed
static void DoTraceHideCEntryFPAddress(unsigned int fp) {
v8::internal::Address saved_c_frame_fp = *(Top::c_entry_fp_address());
CHECK(saved_c_frame_fp);
*(Top::c_entry_fp_address()) = 0;
DoTrace(fp);
*(Top::c_entry_fp_address()) = saved_c_frame_fp;
}
static void CheckRetAddrIsInFunction(const char* func_name,
unsigned int ret_addr,
unsigned int func_start_addr,
unsigned int func_len) {
printf("CheckRetAddrIsInFunction \"%s\": %08x %08x %08x\n",
func_name, func_start_addr, ret_addr, func_start_addr + func_len);
CHECK_GE(ret_addr, func_start_addr);
CHECK_GE(func_start_addr + func_len, ret_addr);
}
static void CheckRetAddrIsInJSFunction(const char* func_name,
unsigned int ret_addr,
Handle<JSFunction> func) {
v8::internal::Code* func_code = func->code();
CheckRetAddrIsInFunction(
func_name, ret_addr,
reinterpret_cast<unsigned int>(func_code->instruction_start()),
func_code->ExecutableSize());
}
// --- T r a c e E x t e n s i o n ---
class TraceExtension : public v8::Extension {
public:
TraceExtension() : v8::Extension("v8/trace", kSource) { }
virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
v8::Handle<String> name);
static v8::Handle<v8::Value> Trace(const v8::Arguments& args);
static v8::Handle<v8::Value> JSTrace(const v8::Arguments& args);
private:
static unsigned int GetFP(const v8::Arguments& args);
static const char* kSource;
};
const char* TraceExtension::kSource =
"native function trace();"
"native function js_trace();";
v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction(
v8::Handle<String> name) {
if (name->Equals(String::New("trace"))) {
return v8::FunctionTemplate::New(TraceExtension::Trace);
} else if (name->Equals(String::New("js_trace"))) {
return v8::FunctionTemplate::New(TraceExtension::JSTrace);
} else {
CHECK(false);
return v8::Handle<v8::FunctionTemplate>();
}
}
unsigned int TraceExtension::GetFP(const v8::Arguments& args) {
CHECK_EQ(1, args.Length());
unsigned int fp = args[0]->Int32Value() << 2;
printf("Trace: %08x\n", fp);
return fp;
}
v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) {
DoTrace(GetFP(args));
return v8::Undefined();
}
v8::Handle<v8::Value> TraceExtension::JSTrace(const v8::Arguments& args) {
DoTraceHideCEntryFPAddress(GetFP(args));
return v8::Undefined();
}
static TraceExtension kTraceExtension;
v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension);
static void InitializeVM() {
if (env.IsEmpty()) {
v8::HandleScope scope;
const char* extensions[] = { "v8/trace" };
v8::ExtensionConfiguration config(1, extensions);
env = v8::Context::New(&config);
}
v8::HandleScope scope;
env->Enter();
}
static Handle<JSFunction> CompileFunction(const char* source) {
return v8::Utils::OpenHandle(*Script::Compile(String::New(source)));
}
static void CompileRun(const char* source) {
Script::Compile(String::New(source))->Run();
}
static Local<Value> GetGlobalProperty(const char* name) {
return env->Global()->Get(String::New(name));
}
static Handle<JSFunction> GetGlobalJSFunction(const char* name) {
Handle<JSFunction> js_func(JSFunction::cast(
*(v8::Utils::OpenHandle(
*GetGlobalProperty(name)))));
return js_func;
}
static void CheckRetAddrIsInJSFunction(const char* func_name,
unsigned int ret_addr) {
CheckRetAddrIsInJSFunction(func_name, ret_addr,
GetGlobalJSFunction(func_name));
}
static void SetGlobalProperty(const char* name, Local<Value> value) {
env->Global()->Set(String::New(name), value);
}
static bool Patch(byte* from,
size_t num,
byte* original,
byte* patch,
size_t patch_len) {
byte* to = from + num;
do {
from = static_cast<byte*>(memchr(from, *original, to - from));
CHECK(from != NULL);
if (memcmp(original, from, patch_len) == 0) {
memcpy(from, patch, patch_len);
return true;
} else {
from++;
}
} while (to - from > 0);
return false;
}
// Creates a global function named 'func_name' that calls the tracing
// function 'trace_func_name' with an actual EBP register value,
// shifted right to be presented as Smi.
static void CreateTraceCallerFunction(const char* func_name,
const char* trace_func_name) {
::v8::internal::EmbeddedVector<char, 256> trace_call_buf;
::v8::internal::OS::SNPrintF(trace_call_buf, "%s(0x6666);", trace_func_name);
Handle<JSFunction> func = CompileFunction(trace_call_buf.start());
CHECK(!func.is_null());
v8::internal::Code* func_code = func->code();
CHECK(func_code->IsCode());
// push 0xcccc (= 0x6666 << 1)
byte original[] = { 0x68, 0xcc, 0xcc, 0x00, 0x00 };
// mov eax,ebp; shr eax; push eax;
byte patch[] = { 0x89, 0xe8, 0xd1, 0xe8, 0x50 };
// Patch generated code to replace pushing of a constant with
// pushing of ebp contents in a Smi
CHECK(Patch(func_code->instruction_start(),
func_code->instruction_size(),
original, patch, sizeof(patch)));
SetGlobalProperty(func_name, v8::ToApi<Value>(func));
}
TEST(CFromJSStackTrace) {
TickSample sample;
StackTracer tracer(reinterpret_cast<unsigned int>(&sample));
InitTraceEnv(&tracer, &sample);
InitializeVM();
v8::HandleScope scope;
CreateTraceCallerFunction("JSFuncDoTrace", "trace");
CompileRun(
"function JSTrace() {"
" JSFuncDoTrace();"
"};\n"
"JSTrace();");
CHECK_GT(sample.frames_count, 1);
// Stack sampling will start from the first JS function, i.e. "JSFuncDoTrace"
CheckRetAddrIsInJSFunction("JSFuncDoTrace",
reinterpret_cast<unsigned int>(sample.stack[0]));
CheckRetAddrIsInJSFunction("JSTrace",
reinterpret_cast<unsigned int>(sample.stack[1]));
}
TEST(PureJSStackTrace) {
TickSample sample;
StackTracer tracer(reinterpret_cast<unsigned int>(&sample));
InitTraceEnv(&tracer, &sample);
InitializeVM();
v8::HandleScope scope;
CreateTraceCallerFunction("JSFuncDoTrace", "js_trace");
CompileRun(
"function JSTrace() {"
" JSFuncDoTrace();"
"};\n"
"function OuterJSTrace() {"
" JSTrace();"
"};\n"
"OuterJSTrace();");
CHECK_GT(sample.frames_count, 1);
// Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
CheckRetAddrIsInJSFunction("JSTrace",
reinterpret_cast<unsigned int>(sample.stack[0]));
CheckRetAddrIsInJSFunction("OuterJSTrace",
reinterpret_cast<unsigned int>(sample.stack[1]));
}
static void CFuncDoTrace() {
unsigned int fp;
#ifdef __GNUC__
fp = reinterpret_cast<unsigned int>(__builtin_frame_address(0));
#elif defined _MSC_VER
__asm mov [fp], ebp // NOLINT
#endif
DoTrace(fp);
}
static int CFunc(int depth) {
if (depth <= 0) {
CFuncDoTrace();
return 0;
} else {
return CFunc(depth - 1) + 1;
}
}
TEST(PureCStackTrace) {
TickSample sample;
StackTracer tracer(reinterpret_cast<unsigned int>(&sample));
InitTraceEnv(&tracer, &sample);
// Check that sampler doesn't crash
CHECK_EQ(10, CFunc(10));
}
#endif // ENABLE_LOGGING_AND_PROFILING