普通文本  |  413行  |  13.32 KB

// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Tests of profiler-related functions from log.h

#include <stdlib.h>

#include "v8.h"

#include "api.h"
#include "codegen.h"
#include "log.h"
#include "isolate.h"
#include "cctest.h"
#include "disassembler.h"
#include "vm-state-inl.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::Address;
using v8::internal::Handle;
using v8::internal::Isolate;
using v8::internal::JSFunction;
using v8::internal::StackTracer;
using v8::internal::TickSample;


static v8::Persistent<v8::Context> env;


static struct {
  TickSample* sample;
} trace_env = { NULL };


static void InitTraceEnv(TickSample* sample) {
  trace_env.sample = sample;
}


static void DoTrace(Address fp) {
  trace_env.sample->fp = fp;
  // sp is only used to define stack high bound
  trace_env.sample->sp =
      reinterpret_cast<Address>(trace_env.sample) - 10240;
  StackTracer::Trace(Isolate::Current(), trace_env.sample);
}


// Hide c_entry_fp to emulate situation when sampling is done while
// pure JS code is being executed
static void DoTraceHideCEntryFPAddress(Address fp) {
  v8::internal::Address saved_c_frame_fp =
      *(Isolate::Current()->c_entry_fp_address());
  CHECK(saved_c_frame_fp);
  *(Isolate::Current()->c_entry_fp_address()) = 0;
  DoTrace(fp);
  *(Isolate::Current()->c_entry_fp_address()) = saved_c_frame_fp;
}


// --- 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);
  static v8::Handle<v8::Value> JSEntrySP(const v8::Arguments& args);
  static v8::Handle<v8::Value> JSEntrySPLevel2(const v8::Arguments& args);
 private:
  static Address GetFP(const v8::Arguments& args);
  static const char* kSource;
};


const char* TraceExtension::kSource =
    "native function trace();"
    "native function js_trace();"
    "native function js_entry_sp();"
    "native function js_entry_sp_level2();";

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 if (name->Equals(String::New("js_entry_sp"))) {
    return v8::FunctionTemplate::New(TraceExtension::JSEntrySP);
  } else if (name->Equals(String::New("js_entry_sp_level2"))) {
    return v8::FunctionTemplate::New(TraceExtension::JSEntrySPLevel2);
  } else {
    CHECK(false);
    return v8::Handle<v8::FunctionTemplate>();
  }
}


Address TraceExtension::GetFP(const v8::Arguments& args) {
  // Convert frame pointer from encoding as smis in the arguments to a pointer.
  CHECK_EQ(2, args.Length());  // Ignore second argument on 32-bit platform.
#if defined(V8_HOST_ARCH_32_BIT)
  Address fp = *reinterpret_cast<Address*>(*args[0]);
#elif defined(V8_HOST_ARCH_64_BIT)
  int64_t low_bits = *reinterpret_cast<uint64_t*>(*args[0]) >> 32;
  int64_t high_bits = *reinterpret_cast<uint64_t*>(*args[1]);
  Address fp = reinterpret_cast<Address>(high_bits | low_bits);
#else
#error Host architecture is neither 32-bit nor 64-bit.
#endif
  printf("Trace: %p\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 Address GetJsEntrySp() {
  CHECK_NE(NULL, i::Isolate::Current()->thread_local_top());
  return Isolate::js_entry_sp(i::Isolate::Current()->thread_local_top());
}


v8::Handle<v8::Value> TraceExtension::JSEntrySP(const v8::Arguments& args) {
  CHECK_NE(0, GetJsEntrySp());
  return v8::Undefined();
}


v8::Handle<v8::Value> TraceExtension::JSEntrySPLevel2(
    const v8::Arguments& args) {
  v8::HandleScope scope;
  const Address js_entry_sp = GetJsEntrySp();
  CHECK_NE(0, js_entry_sp);
  CompileRun("js_entry_sp();");
  CHECK_EQ(js_entry_sp, GetJsEntrySp());
  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 bool IsAddressWithinFuncCode(JSFunction* function, Address addr) {
  i::Code* code = function->code();
  return code->contains(addr);
}

static bool IsAddressWithinFuncCode(const char* func_name, Address addr) {
  v8::Local<v8::Value> func = env->Global()->Get(v8_str(func_name));
  CHECK(func->IsFunction());
  JSFunction* js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
  return IsAddressWithinFuncCode(js_func, addr);
}


// This C++ function is called as a constructor, to grab the frame pointer
// from the calling function.  When this function runs, the stack contains
// a C_Entry frame and a Construct frame above the calling function's frame.
static v8::Handle<Value> construct_call(const v8::Arguments& args) {
  i::StackFrameIterator frame_iterator;
  CHECK(frame_iterator.frame()->is_exit());
  frame_iterator.Advance();
  CHECK(frame_iterator.frame()->is_construct());
  frame_iterator.Advance();
  i::StackFrame* calling_frame = frame_iterator.frame();
  CHECK(calling_frame->is_java_script());

#if defined(V8_HOST_ARCH_32_BIT)
  int32_t low_bits = reinterpret_cast<int32_t>(calling_frame->fp());
  args.This()->Set(v8_str("low_bits"), v8_num(low_bits >> 1));
#elif defined(V8_HOST_ARCH_64_BIT)
  uint64_t fp = reinterpret_cast<uint64_t>(calling_frame->fp());
  int32_t low_bits = static_cast<int32_t>(fp & 0xffffffff);
  int32_t high_bits = static_cast<int32_t>(fp >> 32);
  args.This()->Set(v8_str("low_bits"), v8_num(low_bits));
  args.This()->Set(v8_str("high_bits"), v8_num(high_bits));
#else
#error Host architecture is neither 32-bit nor 64-bit.
#endif
  return args.This();
}


// Use the API to create a JSFunction object that calls the above C++ function.
void CreateFramePointerGrabberConstructor(const char* constructor_name) {
    Local<v8::FunctionTemplate> constructor_template =
        v8::FunctionTemplate::New(construct_call);
    constructor_template->SetClassName(v8_str("FPGrabber"));
    Local<Function> fun = constructor_template->GetFunction();
    env->Global()->Set(v8_str(constructor_name), fun);
}


// Creates a global function named 'func_name' that calls the tracing
// function 'trace_func_name' with an actual EBP register value,
// encoded as one or two Smis.
static void CreateTraceCallerFunction(const char* func_name,
                                      const char* trace_func_name) {
  i::EmbeddedVector<char, 256> trace_call_buf;
  i::OS::SNPrintF(trace_call_buf,
                  "function %s() {"
                  "  fp = new FPGrabber();"
                  "  %s(fp.low_bits, fp.high_bits);"
                  "}",
                  func_name, trace_func_name);

  // Create the FPGrabber function, which grabs the caller's frame pointer
  // when called as a constructor.
  CreateFramePointerGrabberConstructor("FPGrabber");

  // Compile the script.
  CompileRun(trace_call_buf.start());
}


// This test verifies that stack tracing works when called during
// execution of a native function called from JS code. In this case,
// StackTracer uses Isolate::c_entry_fp as a starting point for stack
// walking.
TEST(CFromJSStackTrace) {
  // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
  i::FLAG_use_inlining = false;

  TickSample sample;
  InitTraceEnv(&sample);

  InitializeVM();
  v8::HandleScope scope;
  // Create global function JSFuncDoTrace which calls
  // extension function trace() with the current frame pointer value.
  CreateTraceCallerFunction("JSFuncDoTrace", "trace");
  Local<Value> result = CompileRun(
      "function JSTrace() {"
      "         JSFuncDoTrace();"
      "};\n"
      "JSTrace();\n"
      "true;");
  CHECK(!result.IsEmpty());
  // When stack tracer is invoked, the stack should look as follows:
  // script [JS]
  //   JSTrace() [JS]
  //     JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
  //       trace(EBP) [native (extension)]
  //         DoTrace(EBP) [native]
  //           StackTracer::Trace

  CHECK(sample.has_external_callback);
  CHECK_EQ(FUNCTION_ADDR(TraceExtension::Trace), sample.external_callback);

  // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
  int base = 0;
  CHECK_GT(sample.frames_count, base + 1);

  CHECK(IsAddressWithinFuncCode("JSFuncDoTrace", sample.stack[base + 0]));
  CHECK(IsAddressWithinFuncCode("JSTrace", sample.stack[base + 1]));
}


// This test verifies that stack tracing works when called during
// execution of JS code. However, as calling StackTracer requires
// entering native code, we can only emulate pure JS by erasing
// Isolate::c_entry_fp value. In this case, StackTracer uses passed frame
// pointer value as a starting point for stack walking.
TEST(PureJSStackTrace) {
  // This test does not pass with inlining enabled since inlined functions
  // don't appear in the stack trace.
  i::FLAG_use_inlining = false;

  TickSample sample;
  InitTraceEnv(&sample);

  InitializeVM();
  v8::HandleScope scope;
  // Create global function JSFuncDoTrace which calls
  // extension function js_trace() with the current frame pointer value.
  CreateTraceCallerFunction("JSFuncDoTrace", "js_trace");
  Local<Value> result = CompileRun(
      "function JSTrace() {"
      "         JSFuncDoTrace();"
      "};\n"
      "function OuterJSTrace() {"
      "         JSTrace();"
      "};\n"
      "OuterJSTrace();\n"
      "true;");
  CHECK(!result.IsEmpty());
  // When stack tracer is invoked, the stack should look as follows:
  // script [JS]
  //   OuterJSTrace() [JS]
  //     JSTrace() [JS]
  //       JSFuncDoTrace() [JS]
  //         js_trace(EBP) [native (extension)]
  //           DoTraceHideCEntryFPAddress(EBP) [native]
  //             StackTracer::Trace
  //

  CHECK(sample.has_external_callback);
  CHECK_EQ(FUNCTION_ADDR(TraceExtension::JSTrace), sample.external_callback);

  // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
  int base = 0;
  CHECK_GT(sample.frames_count, base + 1);
  CHECK(IsAddressWithinFuncCode("JSTrace", sample.stack[base + 0]));
  CHECK(IsAddressWithinFuncCode("OuterJSTrace", sample.stack[base + 1]));
}


static void CFuncDoTrace(byte dummy_parameter) {
  Address fp;
#ifdef __GNUC__
  fp = reinterpret_cast<Address>(__builtin_frame_address(0));
#elif defined _MSC_VER
  // Approximate a frame pointer address. We compile without base pointers,
  // so we can't trust ebp/rbp.
  fp = &dummy_parameter - 2 * sizeof(void*);  // NOLINT
#else
#error Unexpected platform.
#endif
  DoTrace(fp);
}


static int CFunc(int depth) {
  if (depth <= 0) {
    CFuncDoTrace(0);
    return 0;
  } else {
    return CFunc(depth - 1) + 1;
  }
}


// This test verifies that stack tracing doesn't crash when called on
// pure native code. StackTracer only unrolls JS code, so we can't
// get any meaningful info here.
TEST(PureCStackTrace) {
  TickSample sample;
  InitTraceEnv(&sample);
  InitializeVM();
  // Check that sampler doesn't crash
  CHECK_EQ(10, CFunc(10));
}


TEST(JsEntrySp) {
  InitializeVM();
  v8::HandleScope scope;
  CHECK_EQ(0, GetJsEntrySp());
  CompileRun("a = 1; b = a + 1;");
  CHECK_EQ(0, GetJsEntrySp());
  CompileRun("js_entry_sp();");
  CHECK_EQ(0, GetJsEntrySp());
  CompileRun("js_entry_sp_level2();");
  CHECK_EQ(0, GetJsEntrySp());
}