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