Home | History | Annotate | Download | only in cctest
      1 // Copyright 2011 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 //
     28 // Tests of profiler-related functions from log.h
     29 
     30 #include <stdlib.h>
     31 
     32 #include "src/v8.h"
     33 
     34 #include "src/api.h"
     35 #include "src/codegen.h"
     36 #include "src/disassembler.h"
     37 #include "src/isolate.h"
     38 #include "src/log.h"
     39 #include "src/profiler/tick-sample.h"
     40 #include "src/vm-state-inl.h"
     41 #include "test/cctest/cctest.h"
     42 #include "test/cctest/trace-extension.h"
     43 
     44 using v8::Function;
     45 using v8::Local;
     46 using v8::Object;
     47 using v8::Script;
     48 using v8::String;
     49 using v8::Value;
     50 
     51 using v8::internal::byte;
     52 using v8::internal::Address;
     53 using v8::internal::Handle;
     54 using v8::internal::Isolate;
     55 using v8::internal::JSFunction;
     56 using v8::internal::TickSample;
     57 
     58 
     59 static bool IsAddressWithinFuncCode(JSFunction* function, Address addr) {
     60   i::AbstractCode* code = function->abstract_code();
     61   return code->contains(addr);
     62 }
     63 
     64 
     65 static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context,
     66                                     const char* func_name,
     67                                     Address addr) {
     68   v8::Local<v8::Value> func =
     69       context->Global()->Get(context, v8_str(func_name)).ToLocalChecked();
     70   CHECK(func->IsFunction());
     71   JSFunction* js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
     72   return IsAddressWithinFuncCode(js_func, addr);
     73 }
     74 
     75 
     76 // This C++ function is called as a constructor, to grab the frame pointer
     77 // from the calling function.  When this function runs, the stack contains
     78 // a C_Entry frame and a Construct frame above the calling function's frame.
     79 static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) {
     80   i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
     81   i::StackFrameIterator frame_iterator(isolate);
     82   CHECK(frame_iterator.frame()->is_exit());
     83   frame_iterator.Advance();
     84   CHECK(frame_iterator.frame()->is_construct());
     85   frame_iterator.Advance();
     86   if (i::FLAG_ignition) {
     87     // Skip over bytecode handler frame.
     88     CHECK(frame_iterator.frame()->type() == i::StackFrame::STUB);
     89     frame_iterator.Advance();
     90   }
     91   i::StackFrame* calling_frame = frame_iterator.frame();
     92   CHECK(calling_frame->is_java_script());
     93 
     94   v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
     95 #if defined(V8_HOST_ARCH_32_BIT)
     96   int32_t low_bits = reinterpret_cast<int32_t>(calling_frame->fp());
     97   args.This()
     98       ->Set(context, v8_str("low_bits"), v8_num(low_bits >> 1))
     99       .FromJust();
    100 #elif defined(V8_HOST_ARCH_64_BIT)
    101   uint64_t fp = reinterpret_cast<uint64_t>(calling_frame->fp());
    102   int32_t low_bits = static_cast<int32_t>(fp & 0xffffffff);
    103   int32_t high_bits = static_cast<int32_t>(fp >> 32);
    104   args.This()->Set(context, v8_str("low_bits"), v8_num(low_bits)).FromJust();
    105   args.This()->Set(context, v8_str("high_bits"), v8_num(high_bits)).FromJust();
    106 #else
    107 #error Host architecture is neither 32-bit nor 64-bit.
    108 #endif
    109   args.GetReturnValue().Set(args.This());
    110 }
    111 
    112 
    113 // Use the API to create a JSFunction object that calls the above C++ function.
    114 void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context,
    115                                           const char* constructor_name) {
    116     Local<v8::FunctionTemplate> constructor_template =
    117         v8::FunctionTemplate::New(context->GetIsolate(), construct_call);
    118     constructor_template->SetClassName(v8_str("FPGrabber"));
    119     Local<Function> fun =
    120         constructor_template->GetFunction(context).ToLocalChecked();
    121     context->Global()->Set(context, v8_str(constructor_name), fun).FromJust();
    122 }
    123 
    124 
    125 // Creates a global function named 'func_name' that calls the tracing
    126 // function 'trace_func_name' with an actual EBP register value,
    127 // encoded as one or two Smis.
    128 static void CreateTraceCallerFunction(v8::Local<v8::Context> context,
    129                                       const char* func_name,
    130                                       const char* trace_func_name) {
    131   i::EmbeddedVector<char, 256> trace_call_buf;
    132   i::SNPrintF(trace_call_buf,
    133               "function %s() {"
    134               "  fp = new FPGrabber();"
    135               "  %s(fp.low_bits, fp.high_bits);"
    136               "}",
    137               func_name, trace_func_name);
    138 
    139   // Create the FPGrabber function, which grabs the caller's frame pointer
    140   // when called as a constructor.
    141   CreateFramePointerGrabberConstructor(context, "FPGrabber");
    142 
    143   // Compile the script.
    144   CompileRun(trace_call_buf.start());
    145 }
    146 
    147 
    148 // This test verifies that stack tracing works when called during
    149 // execution of a native function called from JS code. In this case,
    150 // TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack
    151 // walking.
    152 TEST(CFromJSStackTrace) {
    153   // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
    154   i::FLAG_turbo_inlining = false;
    155   i::FLAG_use_inlining = false;
    156 
    157   TickSample sample;
    158   i::TraceExtension::InitTraceEnv(&sample);
    159 
    160   v8::HandleScope scope(CcTest::isolate());
    161   v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
    162   v8::Context::Scope context_scope(context);
    163 
    164   // Create global function JSFuncDoTrace which calls
    165   // extension function trace() with the current frame pointer value.
    166   CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace");
    167   Local<Value> result = CompileRun(
    168       "function JSTrace() {"
    169       "         JSFuncDoTrace();"
    170       "};\n"
    171       "JSTrace();\n"
    172       "true;");
    173   CHECK(!result.IsEmpty());
    174   // When stack tracer is invoked, the stack should look as follows:
    175   // script [JS]
    176   //   JSTrace() [JS]
    177   //     JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
    178   //       trace(EBP) [native (extension)]
    179   //         DoTrace(EBP) [native]
    180   //           TickSample::Trace
    181 
    182   CHECK(sample.has_external_callback);
    183   CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace),
    184            sample.external_callback_entry);
    185 
    186   // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
    187   unsigned base = 0;
    188   CHECK_GT(sample.frames_count, base + 1);
    189 
    190   CHECK(IsAddressWithinFuncCode(
    191       context, "JSFuncDoTrace", sample.stack[base + 0]));
    192   CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1]));
    193 }
    194 
    195 
    196 // This test verifies that stack tracing works when called during
    197 // execution of JS code. However, as calling TickSample::Trace requires
    198 // entering native code, we can only emulate pure JS by erasing
    199 // Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame
    200 // pointer value as a starting point for stack walking.
    201 TEST(PureJSStackTrace) {
    202   // This test does not pass with inlining enabled since inlined functions
    203   // don't appear in the stack trace.
    204   i::FLAG_turbo_inlining = false;
    205   i::FLAG_use_inlining = false;
    206 
    207   TickSample sample;
    208   i::TraceExtension::InitTraceEnv(&sample);
    209 
    210   v8::HandleScope scope(CcTest::isolate());
    211   v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
    212   v8::Context::Scope context_scope(context);
    213 
    214   // Create global function JSFuncDoTrace which calls
    215   // extension function js_trace() with the current frame pointer value.
    216   CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace");
    217   Local<Value> result = CompileRun(
    218       "function JSTrace() {"
    219       "         JSFuncDoTrace();"
    220       "};\n"
    221       "function OuterJSTrace() {"
    222       "         JSTrace();"
    223       "};\n"
    224       "OuterJSTrace();\n"
    225       "true;");
    226   CHECK(!result.IsEmpty());
    227   // When stack tracer is invoked, the stack should look as follows:
    228   // script [JS]
    229   //   OuterJSTrace() [JS]
    230   //     JSTrace() [JS]
    231   //       JSFuncDoTrace() [JS]
    232   //         js_trace(EBP) [native (extension)]
    233   //           DoTraceHideCEntryFPAddress(EBP) [native]
    234   //             TickSample::Trace
    235   //
    236 
    237   CHECK(sample.has_external_callback);
    238   CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace),
    239            sample.external_callback_entry);
    240 
    241   // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
    242   unsigned base = 0;
    243   CHECK_GT(sample.frames_count, base + 1);
    244   CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0]));
    245   CHECK(IsAddressWithinFuncCode(
    246       context, "OuterJSTrace", sample.stack[base + 1]));
    247 }
    248 
    249 
    250 static void CFuncDoTrace(byte dummy_parameter) {
    251   Address fp;
    252 #if V8_HAS_BUILTIN_FRAME_ADDRESS
    253   fp = reinterpret_cast<Address>(__builtin_frame_address(0));
    254 #elif V8_CC_MSVC
    255   // Approximate a frame pointer address. We compile without base pointers,
    256   // so we can't trust ebp/rbp.
    257   fp = &dummy_parameter - 2 * sizeof(void*);  // NOLINT
    258 #else
    259 #error Unexpected platform.
    260 #endif
    261   i::TraceExtension::DoTrace(fp);
    262 }
    263 
    264 
    265 static int CFunc(int depth) {
    266   if (depth <= 0) {
    267     CFuncDoTrace(0);
    268     return 0;
    269   } else {
    270     return CFunc(depth - 1) + 1;
    271   }
    272 }
    273 
    274 
    275 // This test verifies that stack tracing doesn't crash when called on
    276 // pure native code. TickSample::Trace only unrolls JS code, so we can't
    277 // get any meaningful info here.
    278 TEST(PureCStackTrace) {
    279   TickSample sample;
    280   i::TraceExtension::InitTraceEnv(&sample);
    281   v8::HandleScope scope(CcTest::isolate());
    282   v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
    283   v8::Context::Scope context_scope(context);
    284   // Check that sampler doesn't crash
    285   CHECK_EQ(10, CFunc(10));
    286 }
    287 
    288 
    289 TEST(JsEntrySp) {
    290   v8::HandleScope scope(CcTest::isolate());
    291   v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
    292   v8::Context::Scope context_scope(context);
    293   CHECK(!i::TraceExtension::GetJsEntrySp());
    294   CompileRun("a = 1; b = a + 1;");
    295   CHECK(!i::TraceExtension::GetJsEntrySp());
    296   CompileRun("js_entry_sp();");
    297   CHECK(!i::TraceExtension::GetJsEntrySp());
    298   CompileRun("js_entry_sp_level2();");
    299   CHECK(!i::TraceExtension::GetJsEntrySp());
    300 }
    301