Adding unit tests for profiler's stack tracer.

The testing is a bit tricky because we need to obtain a frame
pointer (EBP on IA-32) from inside of a function. This is especially
interesting in case of a compiled JavaScript function.

Review URL: http://codereview.chromium.org/28112

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1365 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 1e42e700
......@@ -133,6 +133,22 @@ class Profiler: public Thread {
bool Profiler::paused_ = false;
//
// StackTracer implementation
//
void StackTracer::Trace(TickSample* sample) {
// Assuming that stack grows from lower addresses
if (sample->sp < sample->fp && sample->fp < low_stack_bound_) {
sample->InitStack(1);
sample->stack[0] = Memory::Address_at(
(Address)(sample->fp + StandardFrameConstants::kCallerPCOffset));
} else {
// FP seems to be in some intermediate state, better discard this sample
sample->InitStack(0);
}
}
//
// Ticker used to provide ticks to the profiler and the sliding state
// window.
......@@ -141,12 +157,12 @@ class Ticker: public Sampler {
public:
explicit Ticker(int interval, unsigned int low_stack_bound):
Sampler(interval, FLAG_prof), window_(NULL), profiler_(NULL),
low_stack_bound_(low_stack_bound) {}
stack_tracer_(low_stack_bound) {}
~Ticker() { if (IsActive()) Stop(); }
void Tick(TickSample* sample) {
if (IsProfiling()) SampleStack(sample);
if (IsProfiling()) stack_tracer_.Trace(sample);
if (profiler_) profiler_->Insert(sample);
if (window_) window_->AddState(sample->state);
}
......@@ -172,21 +188,9 @@ class Ticker: public Sampler {
}
private:
void SampleStack(TickSample* sample) {
// Assuming that stack grows from lower addresses
if (sample->sp < sample->fp && sample->fp < low_stack_bound_) {
sample->InitStack(1);
sample->stack[0] = Memory::Address_at(
(Address)(sample->fp + StandardFrameConstants::kCallerPCOffset));
} else {
// FP seems to be in some intermediate state, better discard this sample
sample->InitStack(0);
}
}
SlidingStateWindow* window_;
Profiler* profiler_;
unsigned int low_stack_bound_;
StackTracer stack_tracer_;
};
......
......@@ -272,6 +272,17 @@ class Logger {
};
// Class that extracts stack trace, used for profiling
class StackTracer BASE_EMBEDDED {
public:
StackTracer(unsigned int low_stack_bound): low_stack_bound_(low_stack_bound) {
}
void Trace(TickSample* sample);
private:
unsigned int low_stack_bound_;
};
} } // namespace v8::internal
#endif // V8_LOG_H_
......@@ -42,7 +42,7 @@ SOURCES = {
'test-sockets.cc'
],
'arch:arm': ['test-assembler-arm.cc', 'test-disasm-arm.cc'],
'arch:ia32': ['test-assembler-ia32.cc', 'test-disasm-ia32.cc'],
'arch:ia32': ['test-assembler-ia32.cc', 'test-disasm-ia32.cc', 'test-log-ia32.cc'],
'os:linux': ['test-platform-linux.cc'],
'os:macos': ['test-platform-macos.cc'],
'os:nullos': ['test-platform-nullos.cc'],
......
// Copyright 2006-2009 the V8 project authors. All rights reserved.
//
// Tests of profiler-related functions from log.h
#include <stdlib.h>
#include "v8.h"
#include "log.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;
static v8::Persistent<v8::Context> env;
static struct {
StackTracer* tracer;
TickSample* sample;
} trace_env;
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;
// something that is less than fp
trace_env.sample->sp = trace_env.sample->fp - sizeof(unsigned int);
trace_env.tracer->Trace(trace_env.sample);
}
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
#endif
DoTrace(fp);
}
static void CFunc(int i) {
for (int j = i; j >= 0; --j) {
CFuncDoTrace();
}
}
static void CheckRetAddrIsInFunction(unsigned int ret_addr,
unsigned int func_start_addr,
unsigned int func_len) {
printf("CheckRetAddrIsInFunction: %08x %08x %08x\n",
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);
}
#ifdef DEBUG
static const int kMaxCFuncLen = 0x40; // seems enough for a small C function
static void CheckRetAddrIsInCFunction(unsigned int ret_addr,
unsigned int func_start_addr) {
CheckRetAddrIsInFunction(ret_addr, func_start_addr, kMaxCFuncLen);
}
#endif
TEST(PureCStackTrace) {
TickSample sample;
StackTracer tracer(reinterpret_cast<unsigned int>(&sample));
InitTraceEnv(&tracer, &sample);
CFunc(0);
#ifdef DEBUG
// C stack trace works only in debug mode, in release mode EBP is
// usually treated as a general-purpose register
CheckRetAddrIsInCFunction(reinterpret_cast<unsigned int>(sample.stack[0]),
reinterpret_cast<unsigned int>(&CFunc));
CHECK_EQ(0, sample.stack[1]);
#endif
}
// --- 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<v8::String> name);
static v8::Handle<v8::Value> Trace(const v8::Arguments& args);
private:
static const char* kSource;
};
const char* TraceExtension::kSource = "native function trace();";
v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction(
v8::Handle<v8::String> str) {
return v8::FunctionTemplate::New(TraceExtension::Trace);
}
v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) {
CHECK_EQ(1, args.Length());
unsigned int fp = args[0]->Int32Value() << 2;
printf("Trace: %08x\n", fp);
DoTrace(fp);
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 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 = (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;
}
TEST(PureJSStackTrace) {
TickSample sample;
StackTracer tracer(reinterpret_cast<unsigned int>(&sample));
InitTraceEnv(&tracer, &sample);
InitializeVM();
v8::HandleScope scope;
Handle<JSFunction> call_trace = CompileFunction("trace(0x6666);");
CHECK(!call_trace.is_null());
v8::internal::Code* call_trace_code = call_trace->code();
CHECK(call_trace_code->IsCode());
byte original[] = { 0x68, 0xcc, 0xcc, 0x00, 0x00 }; // push 0xcccc (= 0x6666 << 1)
byte patch[] = { 0x89, 0xe8, 0xd1, 0xe8, 0x50 }; // mov eax,ebp; shr eax; push eax;
// Patch generated code to replace pushing of a constant with
// pushing of ebp contents in a Smi
CHECK(Patch(call_trace_code->instruction_start(),
call_trace_code->instruction_size(),
original, patch, sizeof(patch)));
SetGlobalProperty("JSFuncDoTrace", v8::ToApi<Value>(call_trace));
CompileRun(
"function JSTrace() {"
" JSFuncDoTrace();"
"};\n"
"JSTrace();");
Handle<JSFunction> js_trace(JSFunction::cast(*(v8::Utils::OpenHandle(
*GetGlobalProperty("JSTrace")))));
v8::internal::Code* js_trace_code = js_trace->code();
CheckRetAddrIsInFunction(reinterpret_cast<unsigned int>(sample.stack[0]),
reinterpret_cast<unsigned int>(js_trace_code->instruction_start()),
js_trace_code->instruction_size());
CHECK_EQ(0, sample.stack[1]);
}
......@@ -197,6 +197,10 @@
RelativePath="..\..\test\cctest\test-lock.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-log-ia32.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-mark-compact.cc"
>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment