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 "v8.h"
     33 
     34 #include "api.h"
     35 #include "codegen.h"
     36 #include "log.h"
     37 #include "isolate.h"
     38 #include "cctest.h"
     39 #include "disassembler.h"
     40 #include "vm-state-inl.h"
     41 
     42 using v8::Function;
     43 using v8::Local;
     44 using v8::Object;
     45 using v8::Script;
     46 using v8::String;
     47 using v8::Value;
     48 
     49 using v8::internal::byte;
     50 using v8::internal::Address;
     51 using v8::internal::Handle;
     52 using v8::internal::Isolate;
     53 using v8::internal::JSFunction;
     54 using v8::internal::StackTracer;
     55 using v8::internal::TickSample;
     56 
     57 
     58 static v8::Persistent<v8::Context> env;
     59 
     60 
     61 static struct {
     62   TickSample* sample;
     63 } trace_env = { NULL };
     64 
     65 
     66 static void InitTraceEnv(TickSample* sample) {
     67   trace_env.sample = sample;
     68 }
     69 
     70 
     71 static void DoTrace(Address fp) {
     72   trace_env.sample->fp = fp;
     73   // sp is only used to define stack high bound
     74   trace_env.sample->sp =
     75       reinterpret_cast<Address>(trace_env.sample) - 10240;
     76   StackTracer::Trace(Isolate::Current(), trace_env.sample);
     77 }
     78 
     79 
     80 // Hide c_entry_fp to emulate situation when sampling is done while
     81 // pure JS code is being executed
     82 static void DoTraceHideCEntryFPAddress(Address fp) {
     83   v8::internal::Address saved_c_frame_fp =
     84       *(Isolate::Current()->c_entry_fp_address());
     85   CHECK(saved_c_frame_fp);
     86   *(Isolate::Current()->c_entry_fp_address()) = 0;
     87   DoTrace(fp);
     88   *(Isolate::Current()->c_entry_fp_address()) = saved_c_frame_fp;
     89 }
     90 
     91 
     92 // --- T r a c e   E x t e n s i o n ---
     93 
     94 class TraceExtension : public v8::Extension {
     95  public:
     96   TraceExtension() : v8::Extension("v8/trace", kSource) { }
     97   virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
     98       v8::Handle<String> name);
     99   static v8::Handle<v8::Value> Trace(const v8::Arguments& args);
    100   static v8::Handle<v8::Value> JSTrace(const v8::Arguments& args);
    101   static v8::Handle<v8::Value> JSEntrySP(const v8::Arguments& args);
    102   static v8::Handle<v8::Value> JSEntrySPLevel2(const v8::Arguments& args);
    103  private:
    104   static Address GetFP(const v8::Arguments& args);
    105   static const char* kSource;
    106 };
    107 
    108 
    109 const char* TraceExtension::kSource =
    110     "native function trace();"
    111     "native function js_trace();"
    112     "native function js_entry_sp();"
    113     "native function js_entry_sp_level2();";
    114 
    115 v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction(
    116     v8::Handle<String> name) {
    117   if (name->Equals(String::New("trace"))) {
    118     return v8::FunctionTemplate::New(TraceExtension::Trace);
    119   } else if (name->Equals(String::New("js_trace"))) {
    120     return v8::FunctionTemplate::New(TraceExtension::JSTrace);
    121   } else if (name->Equals(String::New("js_entry_sp"))) {
    122     return v8::FunctionTemplate::New(TraceExtension::JSEntrySP);
    123   } else if (name->Equals(String::New("js_entry_sp_level2"))) {
    124     return v8::FunctionTemplate::New(TraceExtension::JSEntrySPLevel2);
    125   } else {
    126     CHECK(false);
    127     return v8::Handle<v8::FunctionTemplate>();
    128   }
    129 }
    130 
    131 
    132 Address TraceExtension::GetFP(const v8::Arguments& args) {
    133   // Convert frame pointer from encoding as smis in the arguments to a pointer.
    134   CHECK_EQ(2, args.Length());  // Ignore second argument on 32-bit platform.
    135 #if defined(V8_HOST_ARCH_32_BIT)
    136   Address fp = *reinterpret_cast<Address*>(*args[0]);
    137 #elif defined(V8_HOST_ARCH_64_BIT)
    138   int64_t low_bits = *reinterpret_cast<uint64_t*>(*args[0]) >> 32;
    139   int64_t high_bits = *reinterpret_cast<uint64_t*>(*args[1]);
    140   Address fp = reinterpret_cast<Address>(high_bits | low_bits);
    141 #else
    142 #error Host architecture is neither 32-bit nor 64-bit.
    143 #endif
    144   printf("Trace: %p\n", fp);
    145   return fp;
    146 }
    147 
    148 
    149 v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) {
    150   DoTrace(GetFP(args));
    151   return v8::Undefined();
    152 }
    153 
    154 
    155 v8::Handle<v8::Value> TraceExtension::JSTrace(const v8::Arguments& args) {
    156   DoTraceHideCEntryFPAddress(GetFP(args));
    157   return v8::Undefined();
    158 }
    159 
    160 
    161 static Address GetJsEntrySp() {
    162   CHECK_NE(NULL, i::Isolate::Current()->thread_local_top());
    163   return Isolate::js_entry_sp(i::Isolate::Current()->thread_local_top());
    164 }
    165 
    166 
    167 v8::Handle<v8::Value> TraceExtension::JSEntrySP(const v8::Arguments& args) {
    168   CHECK_NE(0, GetJsEntrySp());
    169   return v8::Undefined();
    170 }
    171 
    172 
    173 v8::Handle<v8::Value> TraceExtension::JSEntrySPLevel2(
    174     const v8::Arguments& args) {
    175   v8::HandleScope scope;
    176   const Address js_entry_sp = GetJsEntrySp();
    177   CHECK_NE(0, js_entry_sp);
    178   CompileRun("js_entry_sp();");
    179   CHECK_EQ(js_entry_sp, GetJsEntrySp());
    180   return v8::Undefined();
    181 }
    182 
    183 
    184 static TraceExtension kTraceExtension;
    185 v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension);
    186 
    187 
    188 static void InitializeVM() {
    189   if (env.IsEmpty()) {
    190     v8::HandleScope scope;
    191     const char* extensions[] = { "v8/trace" };
    192     v8::ExtensionConfiguration config(1, extensions);
    193     env = v8::Context::New(&config);
    194   }
    195   v8::HandleScope scope;
    196   env->Enter();
    197 }
    198 
    199 
    200 static bool IsAddressWithinFuncCode(JSFunction* function, Address addr) {
    201   i::Code* code = function->code();
    202   return code->contains(addr);
    203 }
    204 
    205 static bool IsAddressWithinFuncCode(const char* func_name, Address addr) {
    206   v8::Local<v8::Value> func = env->Global()->Get(v8_str(func_name));
    207   CHECK(func->IsFunction());
    208   JSFunction* js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
    209   return IsAddressWithinFuncCode(js_func, addr);
    210 }
    211 
    212 
    213 // This C++ function is called as a constructor, to grab the frame pointer
    214 // from the calling function.  When this function runs, the stack contains
    215 // a C_Entry frame and a Construct frame above the calling function's frame.
    216 static v8::Handle<Value> construct_call(const v8::Arguments& args) {
    217   i::StackFrameIterator frame_iterator;
    218   CHECK(frame_iterator.frame()->is_exit());
    219   frame_iterator.Advance();
    220   CHECK(frame_iterator.frame()->is_construct());
    221   frame_iterator.Advance();
    222   i::StackFrame* calling_frame = frame_iterator.frame();
    223   CHECK(calling_frame->is_java_script());
    224 
    225 #if defined(V8_HOST_ARCH_32_BIT)
    226   int32_t low_bits = reinterpret_cast<int32_t>(calling_frame->fp());
    227   args.This()->Set(v8_str("low_bits"), v8_num(low_bits >> 1));
    228 #elif defined(V8_HOST_ARCH_64_BIT)
    229   uint64_t fp = reinterpret_cast<uint64_t>(calling_frame->fp());
    230   int32_t low_bits = static_cast<int32_t>(fp & 0xffffffff);
    231   int32_t high_bits = static_cast<int32_t>(fp >> 32);
    232   args.This()->Set(v8_str("low_bits"), v8_num(low_bits));
    233   args.This()->Set(v8_str("high_bits"), v8_num(high_bits));
    234 #else
    235 #error Host architecture is neither 32-bit nor 64-bit.
    236 #endif
    237   return args.This();
    238 }
    239 
    240 
    241 // Use the API to create a JSFunction object that calls the above C++ function.
    242 void CreateFramePointerGrabberConstructor(const char* constructor_name) {
    243     Local<v8::FunctionTemplate> constructor_template =
    244         v8::FunctionTemplate::New(construct_call);
    245     constructor_template->SetClassName(v8_str("FPGrabber"));
    246     Local<Function> fun = constructor_template->GetFunction();
    247     env->Global()->Set(v8_str(constructor_name), fun);
    248 }
    249 
    250 
    251 // Creates a global function named 'func_name' that calls the tracing
    252 // function 'trace_func_name' with an actual EBP register value,
    253 // encoded as one or two Smis.
    254 static void CreateTraceCallerFunction(const char* func_name,
    255                                       const char* trace_func_name) {
    256   i::EmbeddedVector<char, 256> trace_call_buf;
    257   i::OS::SNPrintF(trace_call_buf,
    258                   "function %s() {"
    259                   "  fp = new FPGrabber();"
    260                   "  %s(fp.low_bits, fp.high_bits);"
    261                   "}",
    262                   func_name, trace_func_name);
    263 
    264   // Create the FPGrabber function, which grabs the caller's frame pointer
    265   // when called as a constructor.
    266   CreateFramePointerGrabberConstructor("FPGrabber");
    267 
    268   // Compile the script.
    269   CompileRun(trace_call_buf.start());
    270 }
    271 
    272 
    273 // This test verifies that stack tracing works when called during
    274 // execution of a native function called from JS code. In this case,
    275 // StackTracer uses Isolate::c_entry_fp as a starting point for stack
    276 // walking.
    277 TEST(CFromJSStackTrace) {
    278   // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
    279   i::FLAG_use_inlining = false;
    280 
    281   TickSample sample;
    282   InitTraceEnv(&sample);
    283 
    284   InitializeVM();
    285   v8::HandleScope scope;
    286   // Create global function JSFuncDoTrace which calls
    287   // extension function trace() with the current frame pointer value.
    288   CreateTraceCallerFunction("JSFuncDoTrace", "trace");
    289   Local<Value> result = CompileRun(
    290       "function JSTrace() {"
    291       "         JSFuncDoTrace();"
    292       "};\n"
    293       "JSTrace();\n"
    294       "true;");
    295   CHECK(!result.IsEmpty());
    296   // When stack tracer is invoked, the stack should look as follows:
    297   // script [JS]
    298   //   JSTrace() [JS]
    299   //     JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
    300   //       trace(EBP) [native (extension)]
    301   //         DoTrace(EBP) [native]
    302   //           StackTracer::Trace
    303 
    304   CHECK(sample.has_external_callback);
    305   CHECK_EQ(FUNCTION_ADDR(TraceExtension::Trace), sample.external_callback);
    306 
    307   // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
    308   int base = 0;
    309   CHECK_GT(sample.frames_count, base + 1);
    310 
    311   CHECK(IsAddressWithinFuncCode("JSFuncDoTrace", sample.stack[base + 0]));
    312   CHECK(IsAddressWithinFuncCode("JSTrace", sample.stack[base + 1]));
    313 }
    314 
    315 
    316 // This test verifies that stack tracing works when called during
    317 // execution of JS code. However, as calling StackTracer requires
    318 // entering native code, we can only emulate pure JS by erasing
    319 // Isolate::c_entry_fp value. In this case, StackTracer uses passed frame
    320 // pointer value as a starting point for stack walking.
    321 TEST(PureJSStackTrace) {
    322   // This test does not pass with inlining enabled since inlined functions
    323   // don't appear in the stack trace.
    324   i::FLAG_use_inlining = false;
    325 
    326   TickSample sample;
    327   InitTraceEnv(&sample);
    328 
    329   InitializeVM();
    330   v8::HandleScope scope;
    331   // Create global function JSFuncDoTrace which calls
    332   // extension function js_trace() with the current frame pointer value.
    333   CreateTraceCallerFunction("JSFuncDoTrace", "js_trace");
    334   Local<Value> result = CompileRun(
    335       "function JSTrace() {"
    336       "         JSFuncDoTrace();"
    337       "};\n"
    338       "function OuterJSTrace() {"
    339       "         JSTrace();"
    340       "};\n"
    341       "OuterJSTrace();\n"
    342       "true;");
    343   CHECK(!result.IsEmpty());
    344   // When stack tracer is invoked, the stack should look as follows:
    345   // script [JS]
    346   //   OuterJSTrace() [JS]
    347   //     JSTrace() [JS]
    348   //       JSFuncDoTrace() [JS]
    349   //         js_trace(EBP) [native (extension)]
    350   //           DoTraceHideCEntryFPAddress(EBP) [native]
    351   //             StackTracer::Trace
    352   //
    353 
    354   CHECK(sample.has_external_callback);
    355   CHECK_EQ(FUNCTION_ADDR(TraceExtension::JSTrace), sample.external_callback);
    356 
    357   // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
    358   int base = 0;
    359   CHECK_GT(sample.frames_count, base + 1);
    360   CHECK(IsAddressWithinFuncCode("JSTrace", sample.stack[base + 0]));
    361   CHECK(IsAddressWithinFuncCode("OuterJSTrace", sample.stack[base + 1]));
    362 }
    363 
    364 
    365 static void CFuncDoTrace(byte dummy_parameter) {
    366   Address fp;
    367 #ifdef __GNUC__
    368   fp = reinterpret_cast<Address>(__builtin_frame_address(0));
    369 #elif defined _MSC_VER
    370   // Approximate a frame pointer address. We compile without base pointers,
    371   // so we can't trust ebp/rbp.
    372   fp = &dummy_parameter - 2 * sizeof(void*);  // NOLINT
    373 #else
    374 #error Unexpected platform.
    375 #endif
    376   DoTrace(fp);
    377 }
    378 
    379 
    380 static int CFunc(int depth) {
    381   if (depth <= 0) {
    382     CFuncDoTrace(0);
    383     return 0;
    384   } else {
    385     return CFunc(depth - 1) + 1;
    386   }
    387 }
    388 
    389 
    390 // This test verifies that stack tracing doesn't crash when called on
    391 // pure native code. StackTracer only unrolls JS code, so we can't
    392 // get any meaningful info here.
    393 TEST(PureCStackTrace) {
    394   TickSample sample;
    395   InitTraceEnv(&sample);
    396   InitializeVM();
    397   // Check that sampler doesn't crash
    398   CHECK_EQ(10, CFunc(10));
    399 }
    400 
    401 
    402 TEST(JsEntrySp) {
    403   InitializeVM();
    404   v8::HandleScope scope;
    405   CHECK_EQ(0, GetJsEntrySp());
    406   CompileRun("a = 1; b = a + 1;");
    407   CHECK_EQ(0, GetJsEntrySp());
    408   CompileRun("js_entry_sp();");
    409   CHECK_EQ(0, GetJsEntrySp());
    410   CompileRun("js_entry_sp_level2();");
    411   CHECK_EQ(0, GetJsEntrySp());
    412 }
    413