| Index: test/cctest/test-sampler-api.cc
|
| diff --git a/test/cctest/test-sampler-api.cc b/test/cctest/test-sampler-api.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0fba62badc50a938bbde3c7114f5b3ce05638c46
|
| --- /dev/null
|
| +++ b/test/cctest/test-sampler-api.cc
|
| @@ -0,0 +1,387 @@
|
| +// Copyright 2014 the V8 project authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +//
|
| +// Tests the sampling API in include/v8.h
|
| +
|
| +#include <string>
|
| +#include "include/v8.h"
|
| +#include "src/simulator.h"
|
| +#include "src/utils.h"
|
| +#include "src/v8.h"
|
| +#include "test/cctest/cctest.h"
|
| +
|
| +#if V8_OS_POSIX && !V8_OS_CYGWIN && !V8_OS_MACOSX
|
| +#include <ucontext.h>
|
| +
|
| +#elif V8_OS_WIN || V8_OS_CYGWIN // V8_OS_POSIX && !V8_OS_CYGWIN
|
| +#include "src/base/win32-headers.h"
|
| +
|
| +#endif // V8_OS_WIN || V8_OS_CYGWIN
|
| +
|
| +using v8::Local;
|
| +using v8::internal::Address;
|
| +using v8::internal::Isolate;
|
| +
|
| +namespace {
|
| +
|
| +class Sample {
|
| + public:
|
| + enum { kFramesLimit = 255 };
|
| +
|
| + Sample() {}
|
| +
|
| + typedef const void* const* const_iterator;
|
| + const_iterator begin() const { return data_.start(); }
|
| + const_iterator end() const { return &data_[data_.length()]; }
|
| +
|
| + int size() const { return data_.length(); }
|
| + v8::internal::Vector<void*>& data() { return data_; }
|
| +
|
| + private:
|
| + v8::internal::EmbeddedVector<void*, kFramesLimit> data_;
|
| +};
|
| +
|
| +
|
| +// The Sample which CollectSample fills up
|
| +Sample* sample;
|
| +
|
| +// The isolate used in the test
|
| +v8::Isolate* isolate;
|
| +
|
| +// Forward declaration
|
| +// (platform specific implementation at the bottom of this file)
|
| +void FillRegisterState(v8::RegisterState* state);
|
| +
|
| +// The JavaScript calls this function when on full stack depth.
|
| +void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
| + v8::RegisterState state;
|
| + FillRegisterState(&state);
|
| + size_t frames_count = isolate->GetStackSample(
|
| + state, sample->data().start(), static_cast<size_t>(sample->size()));
|
| + CHECK_LE(frames_count, static_cast<size_t>(sample->size()));
|
| + sample->data().Truncate(static_cast<int>(frames_count));
|
| +}
|
| +
|
| +
|
| +// A JavaScript function which takes stack depth
|
| +// (minimum value 2) as an argument.
|
| +// When at the bottom of the recursion,
|
| +// the JavaScript code calls into C++ test code,
|
| +// waiting for the sampler to take a sample.
|
| +static const char* test_function =
|
| + "function func(depth) {"
|
| + " if (depth == 2) CollectSample();"
|
| + " else return func(depth - 1);"
|
| + "}";
|
| +
|
| +} // namespace
|
| +
|
| +
|
| +#define SAMPLER_API_TESTS_BOOTSTRAP() \
|
| + Sample test_sample; \
|
| + sample = &test_sample; \
|
| + isolate = CcTest::isolate(); \
|
| + v8::HandleScope scope(isolate); \
|
| + v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); \
|
| + global->Set(v8::String::NewFromUtf8(isolate, "CollectSample"), \
|
| + v8::FunctionTemplate::New(isolate, CollectSample)); \
|
| + LocalContext env(isolate, NULL, global)
|
| +
|
| +
|
| +TEST(StackDepthIsConsistent) {
|
| + SAMPLER_API_TESTS_BOOTSTRAP();
|
| +
|
| + std::string source(test_function);
|
| + source.append("func(8);");
|
| + v8::Script::Compile(v8::String::NewFromUtf8(isolate, source.c_str()))->Run();
|
| +
|
| + CHECK_EQ(8, sample->size());
|
| +}
|
| +
|
| +
|
| +TEST(StackDepthDoesNotExceedMaxValue) {
|
| + SAMPLER_API_TESTS_BOOTSTRAP();
|
| +
|
| + std::string source(test_function);
|
| + source.append("func(300);");
|
| + v8::Script::Compile(v8::String::NewFromUtf8(isolate, source.c_str()))->Run();
|
| +
|
| + int MAX_SIZE = Sample::kFramesLimit;
|
| + CHECK_EQ(MAX_SIZE, sample->size());
|
| +}
|
| +
|
| +
|
| +namespace {
|
| +std::vector<v8::JitCodeEvent> inner_funcs;
|
| +std::vector<v8::JitCodeEvent> outer_funcs;
|
| +
|
| +void TestJitCodeEventHandler(const v8::JitCodeEvent* event) {
|
| + if (event->type != v8::JitCodeEvent::CODE_ADDED) return;
|
| + std::string name(event->name.str, event->name.len);
|
| + if (name.find("test_sampler_api_inner") != std::string::npos)
|
| + inner_funcs.push_back(*event);
|
| + if (name.find("test_sampler_api_outer") != std::string::npos)
|
| + outer_funcs.push_back(*event);
|
| +}
|
| +
|
| +
|
| +// Note: The arguments.callee stuff is there so that the
|
| +// functions are not optimized away.
|
| +static const char* test_script =
|
| + "function test_sampler_api_inner() {"
|
| + " CollectSample();"
|
| + " return arguments.callee.toString();"
|
| + "}"
|
| + "function test_sampler_api_outer() {"
|
| + " return test_sampler_api_inner() + arguments.callee.toString();"
|
| + "}"
|
| + "test_sampler_api_outer();";
|
| +}
|
| +
|
| +
|
| +// The captured sample should have three pc values.
|
| +// They should fall in the range where the compiled code
|
| +// The expected stack is:
|
| +// bottom of stack [{anon script}, outer, inner] top of stack
|
| +// ^ ^ ^
|
| +// sample.stack indices 2 1 0
|
| +TEST(StackFramesConsistent) {
|
| + SAMPLER_API_TESTS_BOOTSTRAP();
|
| +
|
| + isolate->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
|
| + TestJitCodeEventHandler);
|
| + v8::Script::Compile(v8::String::NewFromUtf8(isolate, test_script))->Run();
|
| +
|
| + CHECK_EQ(3, sample->size());
|
| +
|
| + bool stack_top_is_inner = false;
|
| + bool below_inner_is_outer = false;
|
| +
|
| + for (unsigned i = 0; i < inner_funcs.size(); i++) {
|
| + void* start_addr = inner_funcs[i].code_start;
|
| + void* end_addr = reinterpret_cast<void*>(
|
| + (int64_t)inner_funcs[i].code_start + inner_funcs[i].code_len);
|
| + if ((*sample->begin() >= start_addr) && (*sample->begin() < end_addr))
|
| + stack_top_is_inner = true;
|
| + }
|
| +
|
| + for (unsigned i = 0; i < outer_funcs.size(); i++) {
|
| + void* start_addr = outer_funcs[i].code_start;
|
| + void* end_addr = reinterpret_cast<void*>(
|
| + (int64_t)outer_funcs[i].code_start + outer_funcs[i].code_len);
|
| + if ((*(sample->begin() + 1) >= start_addr) &&
|
| + (*(sample->begin() + 1) < end_addr))
|
| + below_inner_is_outer = true;
|
| + }
|
| +
|
| + CHECK(stack_top_is_inner);
|
| + CHECK(below_inner_is_outer);
|
| +}
|
| +
|
| +
|
| +namespace {
|
| +
|
| +#if defined(USE_SIMULATOR)
|
| +class SimulatorHelper {
|
| + public:
|
| + inline bool Init(v8::Isolate* isolate) {
|
| + simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
|
| + ->thread_local_top()
|
| + ->simulator_;
|
| + // Check if there is active simulator.
|
| + return simulator_ != NULL;
|
| + }
|
| +
|
| + inline void FillRegisters(v8::RegisterState* state) {
|
| +#if V8_TARGET_ARCH_ARM
|
| + state->pc = reinterpret_cast<Address>(simulator_->get_pc());
|
| + state->sp = reinterpret_cast<Address>(
|
| + simulator_->get_register(v8::internal::Simulator::sp));
|
| + state->fp = reinterpret_cast<Address>(
|
| + simulator_->get_register(v8::internal::Simulator::r11));
|
| +#elif V8_TARGET_ARCH_ARM64
|
| + if (simulator_->sp() == 0 || simulator_->fp() == 0) {
|
| + // It possible that the simulator is interrupted while it is updating
|
| + // the sp or fp register. ARM64 simulator does this in two steps:
|
| + // first setting it to zero and then setting it to the new value.
|
| + // Bailout if sp/fp doesn't contain the new value.
|
| + return;
|
| + }
|
| + state->pc = reinterpret_cast<Address>(simulator_->pc());
|
| + state->sp = reinterpret_cast<Address>(simulator_->sp());
|
| + state->fp = reinterpret_cast<Address>(simulator_->fp());
|
| +#elif V8_TARGET_ARCH_MIPS
|
| + state->pc = reinterpret_cast<Address>(simulator_->get_pc());
|
| + state->sp =
|
| + reinterpret_cast<Address>(simulator_->get_register(Simulator::sp));
|
| + state->fp =
|
| + reinterpret_cast<Address>(simulator_->get_register(Simulator::fp));
|
| +#elif V8_TARGET_ARCH_MIPS64
|
| + state->pc = reinterpret_cast<Address>(simulator_->get_pc());
|
| + state->sp = reinterpret_cast<Address>(
|
| + simulator_->get_register(v8::internal::Simulator::sp));
|
| + state->fp = reinterpret_cast<Address>(
|
| + simulator_->get_register(v8::internal::Simulator::fp));
|
| +#endif
|
| + }
|
| +
|
| + private:
|
| + v8::internal::Simulator* simulator_;
|
| +};
|
| +#endif // USE_SIMULATOR
|
| +
|
| +// Platform specific implementation of FillRegisterState
|
| +void FillRegisterState(v8::RegisterState* state) {
|
| +#if defined(USE_SIMULATOR)
|
| + SimulatorHelper helper;
|
| + if (!helper.Init(isolate)) return;
|
| + helper.FillRegisters(state);
|
| + // It's possible that the simulator is interrupted while it is updating
|
| + // the sp or fp register. ARM64 simulator does this in two steps:
|
| + // first setting it to zero and then setting it to a new value.
|
| + // Bailout if sp/fp doesn't contain the new value.
|
| + if (state->sp == 0 || state->fp == 0) return;
|
| +
|
| +#elif V8_OS_MACOSX
|
| + // TODO(alph): MacOSX doesn't support getcontext, so do nothing
|
| + // at the moment. Consider enabling the test by obtaining the
|
| + // context via POSIX signals.
|
| + return;
|
| +
|
| +#elif V8_OS_POSIX && !V8_OS_CYGWIN // defined(USE_SIMULATOR)
|
| + ucontext_t ucontext;
|
| + getcontext(&ucontext);
|
| +#if !V8_OS_OPENBSD
|
| + mcontext_t& mcontext = ucontext.uc_mcontext;
|
| +#endif
|
| +#if V8_OS_LINUX
|
| +#if V8_HOST_ARCH_IA32
|
| + state->pc = reinterpret_cast<void*>(mcontext.gregs[REG_EIP]);
|
| + state->sp = reinterpret_cast<void*>(mcontext.gregs[REG_ESP]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.gregs[REG_EBP]);
|
| +#elif V8_HOST_ARCH_X64
|
| + state->pc = reinterpret_cast<void*>(mcontext.gregs[REG_RIP]);
|
| + state->sp = reinterpret_cast<void*>(mcontext.gregs[REG_RSP]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.gregs[REG_RBP]);
|
| +#elif V8_HOST_ARCH_ARM
|
| +#if defined(__GLIBC__) && !defined(__UCLIBC__) && \
|
| + (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 3))
|
| + // Old GLibc ARM versions used a gregs[] array to access the register
|
| + // values from mcontext_t.
|
| + state->pc = reinterpret_cast<void*>(mcontext.gregs[R15]);
|
| + state->sp = reinterpret_cast<void*>(mcontext.gregs[R13]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.gregs[R11]);
|
| +#else
|
| + state->pc = reinterpret_cast<void*>(mcontext.arm_pc);
|
| + state->sp = reinterpret_cast<void*>(mcontext.arm_sp);
|
| + state->fp = reinterpret_cast<void*>(mcontext.arm_fp);
|
| +#endif // defined(__GLIBC__) && !defined(__UCLIBC__) &&
|
| +// (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 3))
|
| +#elif V8_HOST_ARCH_ARM64
|
| + state->pc = reinterpret_cast<void*>(mcontext.pc);
|
| + state->sp = reinterpret_cast<void*>(mcontext.sp);
|
| + // FP is an alias for x29.
|
| + state->fp = reinterpret_cast<void*>(mcontext.regs[29]);
|
| +#elif V8_HOST_ARCH_MIPS
|
| + state->pc = reinterpret_cast<void*>(mcontext.pc);
|
| + state->sp = reinterpret_cast<void*>(mcontext.gregs[29]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.gregs[30]);
|
| +#elif V8_HOST_ARCH_MIPS64
|
| + state->pc = reinterpret_cast<void*>(mcontext.pc);
|
| + state->sp = reinterpret_cast<void*>(mcontext.gregs[29]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.gregs[30]);
|
| +#endif // V8_HOST_ARCH_*
|
| +#elif V8_OS_MACOSX
|
| +#if V8_HOST_ARCH_X64
|
| +#if __DARWIN_UNIX03
|
| + state->pc = reinterpret_cast<void*>(mcontext->__ss.__rip);
|
| + state->sp = reinterpret_cast<void*>(mcontext->__ss.__rsp);
|
| + state->fp = reinterpret_cast<void*>(mcontext->__ss.__rbp);
|
| +#else // !__DARWIN_UNIX03
|
| + state->pc = reinterpret_cast<void*>(mcontext->ss.rip);
|
| + state->sp = reinterpret_cast<void*>(mcontext->ss.rsp);
|
| + state->fp = reinterpret_cast<void*>(mcontext->ss.rbp);
|
| +#endif // __DARWIN_UNIX03
|
| +#elif V8_HOST_ARCH_IA32
|
| +#if __DARWIN_UNIX03
|
| + state->pc = reinterpret_cast<void*>(mcontext->__ss.__eip);
|
| + state->sp = reinterpret_cast<void*>(mcontext->__ss.__esp);
|
| + state->fp = reinterpret_cast<void*>(mcontext->__ss.__ebp);
|
| +#else // !__DARWIN_UNIX03
|
| + state->pc = reinterpret_cast<void*>(mcontext->ss.eip);
|
| + state->sp = reinterpret_cast<void*>(mcontext->ss.esp);
|
| + state->fp = reinterpret_cast<void*>(mcontext->ss.ebp);
|
| +#endif // __DARWIN_UNIX03
|
| +#endif // V8_HOST_ARCH_IA32
|
| +#elif V8_OS_FREEBSD
|
| +#if V8_HOST_ARCH_IA32
|
| + state->pc = reinterpret_cast<void*>(mcontext.mc_eip);
|
| + state->sp = reinterpret_cast<void*>(mcontext.mc_esp);
|
| + state->fp = reinterpret_cast<void*>(mcontext.mc_ebp);
|
| +#elif V8_HOST_ARCH_X64
|
| + state->pc = reinterpret_cast<void*>(mcontext.mc_rip);
|
| + state->sp = reinterpret_cast<void*>(mcontext.mc_rsp);
|
| + state->fp = reinterpret_cast<void*>(mcontext.mc_rbp);
|
| +#elif V8_HOST_ARCH_ARM
|
| + state->pc = reinterpret_cast<void*>(mcontext.mc_r15);
|
| + state->sp = reinterpret_cast<void*>(mcontext.mc_r13);
|
| + state->fp = reinterpret_cast<void*>(mcontext.mc_r11);
|
| +#endif // V8_HOST_ARCH_*
|
| +#elif V8_OS_NETBSD
|
| +#if V8_HOST_ARCH_IA32
|
| + state->pc = reinterpret_cast<void*>(mcontext.__gregs[_REG_EIP]);
|
| + state->sp = reinterpret_cast<void*>(mcontext.__gregs[_REG_ESP]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.__gregs[_REG_EBP]);
|
| +#elif V8_HOST_ARCH_X64
|
| + state->pc = reinterpret_cast<void*>(mcontext.__gregs[_REG_RIP]);
|
| + state->sp = reinterpret_cast<void*>(mcontext.__gregs[_REG_RSP]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.__gregs[_REG_RBP]);
|
| +#endif // V8_HOST_ARCH_*
|
| +#elif V8_OS_OPENBSD
|
| +#if V8_HOST_ARCH_IA32
|
| + state->pc = reinterpret_cast<void*>(ucontext->sc_eip);
|
| + state->sp = reinterpret_cast<void*>(ucontext->sc_esp);
|
| + state->fp = reinterpret_cast<void*>(ucontext->sc_ebp);
|
| +#elif V8_HOST_ARCH_X64
|
| + state->pc = reinterpret_cast<void*>(ucontext->sc_rip);
|
| + state->sp = reinterpret_cast<void*>(ucontext->sc_rsp);
|
| + state->fp = reinterpret_cast<void*>(ucontext->sc_rbp);
|
| +#endif // V8_HOST_ARCH_*
|
| +#elif V8_OS_SOLARIS
|
| + state->pc = reinterpret_cast<void*>(mcontext.gregs[REG_PC]);
|
| + state->sp = reinterpret_cast<void*>(mcontext.gregs[REG_SP]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.gregs[REG_FP]);
|
| +#elif V8_OS_QNX
|
| +#if V8_HOST_ARCH_IA32
|
| + state->pc = reinterpret_cast<void*>(mcontext.cpu.eip);
|
| + state->sp = reinterpret_cast<void*>(mcontext.cpu.esp);
|
| + state->fp = reinterpret_cast<void*>(mcontext.cpu.ebp);
|
| +#elif V8_HOST_ARCH_ARM
|
| + state->pc = reinterpret_cast<void*>(mcontext.cpu.gpr[ARM_REG_PC]);
|
| + state->sp = reinterpret_cast<void*>(mcontext.cpu.gpr[ARM_REG_SP]);
|
| + state->fp = reinterpret_cast<void*>(mcontext.cpu.gpr[ARM_REG_FP]);
|
| +#endif // V8_HOST_ARCH_*
|
| +#endif // V8_OS_QNX
|
| +
|
| +#elif V8_OS_WIN || V8_OS_CYGWIN // V8_OS_POSIX && !V8_OS_CYGWIN
|
| + CONTEXT context;
|
| + memset(&context, 0, sizeof(context));
|
| + context.ContextFlags = CONTEXT_FULL;
|
| + GetThreadContext(OpenThread(THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME |
|
| + THREAD_QUERY_INFORMATION,
|
| + false, GetCurrentThreadId()),
|
| + &context);
|
| +#if V8_HOST_ARCH_X64
|
| + state->pc = reinterpret_cast<void*>(context.Rip);
|
| + state->sp = reinterpret_cast<void*>(context.Rsp);
|
| + state->fp = reinterpret_cast<void*>(context.Rbp);
|
| +#else
|
| + state->pc = reinterpret_cast<void*>(context.Eip);
|
| + state->sp = reinterpret_cast<void*>(context.Esp);
|
| + state->fp = reinterpret_cast<void*>(context.Ebp);
|
| +#endif // V8_HOST_ARCH_X64
|
| +#endif // V8_OS_WIN || V8_OS_CYGWIN
|
| +}
|
| +
|
| +} // namespace
|
|
|