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