LCOV - code coverage report
Current view: top level - test/cctest - test-log-stack-tracer.cc (source / functions) Hit Total Coverage
Test: app.info Lines: 81 81 100.0 %
Date: 2019-04-17 Functions: 11 11 100.0 %

          Line data    Source code
       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 "include/v8-profiler.h"
      33             : #include "src/api-inl.h"
      34             : #include "src/disassembler.h"
      35             : #include "src/isolate.h"
      36             : #include "src/objects-inl.h"
      37             : #include "src/v8.h"
      38             : #include "src/vm-state-inl.h"
      39             : #include "test/cctest/cctest.h"
      40             : #include "test/cctest/trace-extension.h"
      41             : 
      42             : namespace v8 {
      43             : namespace internal {
      44             : 
      45          20 : static bool IsAddressWithinFuncCode(JSFunction function, void* addr) {
      46          20 :   i::AbstractCode code = function->abstract_code();
      47          40 :   return code->contains(reinterpret_cast<Address>(addr));
      48             : }
      49             : 
      50          20 : static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context,
      51             :                                     const char* func_name, void* addr) {
      52             :   v8::Local<v8::Value> func =
      53          80 :       context->Global()->Get(context, v8_str(func_name)).ToLocalChecked();
      54          20 :   CHECK(func->IsFunction());
      55          20 :   JSFunction js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
      56          20 :   return IsAddressWithinFuncCode(js_func, addr);
      57             : }
      58             : 
      59             : 
      60             : // This C++ function is called as a constructor, to grab the frame pointer
      61             : // from the calling function.  When this function runs, the stack contains
      62             : // a C_Entry frame and a Construct frame above the calling function's frame.
      63          10 : static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) {
      64             :   i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
      65          10 :   i::StackFrameIterator frame_iterator(isolate);
      66          10 :   CHECK(frame_iterator.frame()->is_exit() ||
      67             :         frame_iterator.frame()->is_builtin_exit());
      68          10 :   frame_iterator.Advance();
      69          10 :   CHECK(frame_iterator.frame()->is_construct());
      70          10 :   frame_iterator.Advance();
      71          10 :   if (frame_iterator.frame()->type() == i::StackFrame::STUB) {
      72             :     // Skip over bytecode handler frame.
      73           8 :     frame_iterator.Advance();
      74             :   }
      75             :   i::StackFrame* calling_frame = frame_iterator.frame();
      76          10 :   CHECK(calling_frame->is_java_script());
      77             : 
      78          10 :   v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
      79             : #if defined(V8_HOST_ARCH_32_BIT)
      80             :   int32_t low_bits = static_cast<int32_t>(calling_frame->fp());
      81             :   args.This()
      82             :       ->Set(context, v8_str("low_bits"), v8_num(low_bits >> 1))
      83             :       .FromJust();
      84             : #elif defined(V8_HOST_ARCH_64_BIT)
      85             :   Address fp = calling_frame->fp();
      86             :   uint64_t kSmiValueMask =
      87             :       (static_cast<uintptr_t>(1) << (kSmiValueSize - 1)) - 1;
      88          10 :   int32_t low_bits = static_cast<int32_t>(fp & kSmiValueMask);
      89          10 :   fp >>= kSmiValueSize - 1;
      90          10 :   int32_t high_bits = static_cast<int32_t>(fp & kSmiValueMask);
      91          10 :   fp >>= kSmiValueSize - 1;
      92          10 :   CHECK_EQ(fp, 0);  // Ensure all the bits are successfully encoded.
      93          30 :   args.This()->Set(context, v8_str("low_bits"), v8_int(low_bits)).FromJust();
      94          30 :   args.This()->Set(context, v8_str("high_bits"), v8_int(high_bits)).FromJust();
      95             : #else
      96             : #error Host architecture is neither 32-bit nor 64-bit.
      97             : #endif
      98             :   args.GetReturnValue().Set(args.This());
      99          10 : }
     100             : 
     101             : 
     102             : // Use the API to create a JSFunction object that calls the above C++ function.
     103          10 : void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context,
     104             :                                           const char* constructor_name) {
     105             :     Local<v8::FunctionTemplate> constructor_template =
     106          10 :         v8::FunctionTemplate::New(context->GetIsolate(), construct_call);
     107          10 :     constructor_template->SetClassName(v8_str("FPGrabber"));
     108             :     Local<Function> fun =
     109          10 :         constructor_template->GetFunction(context).ToLocalChecked();
     110          40 :     context->Global()->Set(context, v8_str(constructor_name), fun).FromJust();
     111          10 : }
     112             : 
     113             : 
     114             : // Creates a global function named 'func_name' that calls the tracing
     115             : // function 'trace_func_name' with an actual EBP register value,
     116             : // encoded as one or two Smis.
     117          10 : static void CreateTraceCallerFunction(v8::Local<v8::Context> context,
     118             :                                       const char* func_name,
     119             :                                       const char* trace_func_name) {
     120             :   i::EmbeddedVector<char, 256> trace_call_buf;
     121             :   i::SNPrintF(trace_call_buf,
     122             :               "function %s() {"
     123             :               "  fp = new FPGrabber();"
     124             :               "  %s(fp.low_bits, fp.high_bits);"
     125             :               "}",
     126          10 :               func_name, trace_func_name);
     127             : 
     128             :   // Create the FPGrabber function, which grabs the caller's frame pointer
     129             :   // when called as a constructor.
     130          10 :   CreateFramePointerGrabberConstructor(context, "FPGrabber");
     131             : 
     132             :   // Compile the script.
     133             :   CompileRun(trace_call_buf.start());
     134          10 : }
     135             : 
     136             : 
     137             : // This test verifies that stack tracing works when called during
     138             : // execution of a native function called from JS code. In this case,
     139             : // TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack
     140             : // walking.
     141       26644 : TEST(CFromJSStackTrace) {
     142             :   // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
     143           5 :   i::FLAG_turbo_inlining = false;
     144             : 
     145             :   TickSample sample;
     146           5 :   i::TraceExtension::InitTraceEnv(&sample);
     147             : 
     148          10 :   v8::HandleScope scope(CcTest::isolate());
     149          10 :   v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
     150             :   v8::Context::Scope context_scope(context);
     151             : 
     152             :   // Create global function JSFuncDoTrace which calls
     153             :   // extension function trace() with the current frame pointer value.
     154           5 :   CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace");
     155             :   Local<Value> result = CompileRun(
     156             :       "function JSTrace() {"
     157             :       "         JSFuncDoTrace();"
     158             :       "};\n"
     159             :       "JSTrace();\n"
     160             :       "true;");
     161           5 :   CHECK(!result.IsEmpty());
     162             :   // When stack tracer is invoked, the stack should look as follows:
     163             :   // script [JS]
     164             :   //   JSTrace() [JS]
     165             :   //     JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
     166             :   //       trace(EBP) [native (extension)]
     167             :   //         DoTrace(EBP) [native]
     168             :   //           TickSample::Trace
     169             : 
     170           5 :   CHECK(sample.has_external_callback);
     171           5 :   CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace),
     172             :            reinterpret_cast<Address>(sample.external_callback_entry));
     173             : 
     174             :   // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
     175             :   unsigned base = 0;
     176           5 :   CHECK_GT(sample.frames_count, base + 1);
     177             : 
     178           5 :   CHECK(IsAddressWithinFuncCode(
     179             :       context, "JSFuncDoTrace", sample.stack[base + 0]));
     180           5 :   CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1]));
     181           5 : }
     182             : 
     183             : 
     184             : // This test verifies that stack tracing works when called during
     185             : // execution of JS code. However, as calling TickSample::Trace requires
     186             : // entering native code, we can only emulate pure JS by erasing
     187             : // Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame
     188             : // pointer value as a starting point for stack walking.
     189       26644 : TEST(PureJSStackTrace) {
     190             :   // This test does not pass with inlining enabled since inlined functions
     191             :   // don't appear in the stack trace.
     192           5 :   i::FLAG_turbo_inlining = false;
     193             : 
     194             :   TickSample sample;
     195           5 :   i::TraceExtension::InitTraceEnv(&sample);
     196             : 
     197          10 :   v8::HandleScope scope(CcTest::isolate());
     198          10 :   v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
     199             :   v8::Context::Scope context_scope(context);
     200             : 
     201             :   // Create global function JSFuncDoTrace which calls
     202             :   // extension function js_trace() with the current frame pointer value.
     203           5 :   CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace");
     204             :   Local<Value> result = CompileRun(
     205             :       "function JSTrace() {"
     206             :       "         JSFuncDoTrace();"
     207             :       "};\n"
     208             :       "function OuterJSTrace() {"
     209             :       "         JSTrace();"
     210             :       "};\n"
     211             :       "OuterJSTrace();\n"
     212             :       "true;");
     213           5 :   CHECK(!result.IsEmpty());
     214             :   // When stack tracer is invoked, the stack should look as follows:
     215             :   // script [JS]
     216             :   //   OuterJSTrace() [JS]
     217             :   //     JSTrace() [JS]
     218             :   //       JSFuncDoTrace() [JS]
     219             :   //         js_trace(EBP) [native (extension)]
     220             :   //           DoTraceHideCEntryFPAddress(EBP) [native]
     221             :   //             TickSample::Trace
     222             :   //
     223             : 
     224           5 :   CHECK(sample.has_external_callback);
     225           5 :   CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace),
     226             :            reinterpret_cast<Address>(sample.external_callback_entry));
     227             : 
     228             :   // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
     229             :   unsigned base = 0;
     230           5 :   CHECK_GT(sample.frames_count, base + 1);
     231           5 :   CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0]));
     232           5 :   CHECK(IsAddressWithinFuncCode(
     233             :       context, "OuterJSTrace", sample.stack[base + 1]));
     234           5 : }
     235             : 
     236             : static void CFuncDoTrace(byte dummy_param) {
     237             :   Address fp;
     238             : #if V8_HAS_BUILTIN_FRAME_ADDRESS
     239           5 :   fp = reinterpret_cast<Address>(__builtin_frame_address(0));
     240             : #elif V8_CC_MSVC
     241             :   // Approximate a frame pointer address. We compile without base pointers,
     242             :   // so we can't trust ebp/rbp.
     243             :   fp = reinterpret_cast<Address>(&dummy_param) - 2 * sizeof(void*);  // NOLINT
     244             : #else
     245             : #error Unexpected platform.
     246             : #endif
     247           5 :   i::TraceExtension::DoTrace(fp);
     248             : }
     249             : 
     250             : 
     251             : static int CFunc(int depth) {
     252          55 :   if (depth <= 0) {
     253             :     CFuncDoTrace(0);
     254             :     return 0;
     255             :   } else {
     256          50 :     return CFunc(depth - 1) + 1;
     257             :   }
     258             : }
     259             : 
     260             : 
     261             : // This test verifies that stack tracing doesn't crash when called on
     262             : // pure native code. TickSample::Trace only unrolls JS code, so we can't
     263             : // get any meaningful info here.
     264       26644 : TEST(PureCStackTrace) {
     265             :   TickSample sample;
     266           5 :   i::TraceExtension::InitTraceEnv(&sample);
     267          10 :   v8::HandleScope scope(CcTest::isolate());
     268          10 :   v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
     269             :   v8::Context::Scope context_scope(context);
     270             :   // Check that sampler doesn't crash
     271           5 :   CHECK_EQ(10, CFunc(10));
     272           5 : }
     273             : 
     274             : 
     275       26644 : TEST(JsEntrySp) {
     276          10 :   v8::HandleScope scope(CcTest::isolate());
     277          10 :   v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
     278             :   v8::Context::Scope context_scope(context);
     279           5 :   CHECK(!i::TraceExtension::GetJsEntrySp());
     280             :   CompileRun("a = 1; b = a + 1;");
     281           5 :   CHECK(!i::TraceExtension::GetJsEntrySp());
     282             :   CompileRun("js_entry_sp();");
     283           5 :   CHECK(!i::TraceExtension::GetJsEntrySp());
     284             :   CompileRun("js_entry_sp_level2();");
     285           5 :   CHECK(!i::TraceExtension::GetJsEntrySp());
     286           5 : }
     287             : 
     288             : }  // namespace internal
     289       79917 : }  // namespace v8

Generated by: LCOV version 1.10