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