| OLD | NEW |
| 1 // Copyright 2011 the V8 project authors. All rights reserved. | 1 // Copyright 2011 the V8 project authors. All rights reserved. |
| 2 // Redistribution and use in source and binary forms, with or without | 2 // Redistribution and use in source and binary forms, with or without |
| 3 // modification, are permitted provided that the following conditions are | 3 // modification, are permitted provided that the following conditions are |
| 4 // met: | 4 // met: |
| 5 // | 5 // |
| 6 // * Redistributions of source code must retain the above copyright | 6 // * Redistributions of source code must retain the above copyright |
| 7 // notice, this list of conditions and the following disclaimer. | 7 // notice, this list of conditions and the following disclaimer. |
| 8 // * Redistributions in binary form must reproduce the above | 8 // * Redistributions in binary form must reproduce the above |
| 9 // copyright notice, this list of conditions and the following | 9 // copyright notice, this list of conditions and the following |
| 10 // disclaimer in the documentation and/or other materials provided | 10 // disclaimer in the documentation and/or other materials provided |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 | 31 |
| 32 #include "v8.h" | 32 #include "v8.h" |
| 33 | 33 |
| 34 #include "api.h" | 34 #include "api.h" |
| 35 #include "cctest.h" | 35 #include "cctest.h" |
| 36 #include "codegen.h" | 36 #include "codegen.h" |
| 37 #include "disassembler.h" | 37 #include "disassembler.h" |
| 38 #include "isolate.h" | 38 #include "isolate.h" |
| 39 #include "log.h" | 39 #include "log.h" |
| 40 #include "sampler.h" | 40 #include "sampler.h" |
| 41 #include "trace-extension.h" |
| 41 #include "vm-state-inl.h" | 42 #include "vm-state-inl.h" |
| 42 | 43 |
| 43 using v8::Function; | 44 using v8::Function; |
| 44 using v8::Local; | 45 using v8::Local; |
| 45 using v8::Object; | 46 using v8::Object; |
| 46 using v8::Script; | 47 using v8::Script; |
| 47 using v8::String; | 48 using v8::String; |
| 48 using v8::Value; | 49 using v8::Value; |
| 49 | 50 |
| 50 using v8::internal::byte; | 51 using v8::internal::byte; |
| 51 using v8::internal::Address; | 52 using v8::internal::Address; |
| 52 using v8::internal::Handle; | 53 using v8::internal::Handle; |
| 53 using v8::internal::Isolate; | 54 using v8::internal::Isolate; |
| 54 using v8::internal::JSFunction; | 55 using v8::internal::JSFunction; |
| 55 using v8::internal::RegisterState; | |
| 56 using v8::internal::TickSample; | 56 using v8::internal::TickSample; |
| 57 | 57 |
| 58 | 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(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 | |
| 81 static 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 | |
| 93 class 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 | |
| 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::GetNativeFunctionTemplate( | |
| 116 v8::Isolate* isolate, v8::Handle<String> name) { | |
| 117 if (name->Equals(String::NewFromUtf8(isolate, "trace"))) { | |
| 118 return v8::FunctionTemplate::New(isolate, TraceExtension::Trace); | |
| 119 } else if (name->Equals( | |
| 120 String::NewFromUtf8(isolate, "js_trace"))) { | |
| 121 return v8::FunctionTemplate::New(isolate, TraceExtension::JSTrace); | |
| 122 } else if (name->Equals(String::NewFromUtf8(isolate, "js_entry_sp"))) { | |
| 123 return v8::FunctionTemplate::New(isolate, TraceExtension::JSEntrySP); | |
| 124 } else if (name->Equals(String::NewFromUtf8(isolate, "js_entry_sp_level2"))) { | |
| 125 return v8::FunctionTemplate::New(isolate, TraceExtension::JSEntrySPLevel2); | |
| 126 } else { | |
| 127 CHECK(false); | |
| 128 return v8::Handle<v8::FunctionTemplate>(); | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 | |
| 133 Address 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 | |
| 150 void TraceExtension::Trace(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 151 DoTrace(GetFP(args)); | |
| 152 } | |
| 153 | |
| 154 | |
| 155 void TraceExtension::JSTrace(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 156 DoTraceHideCEntryFPAddress(GetFP(args)); | |
| 157 } | |
| 158 | |
| 159 | |
| 160 static Address GetJsEntrySp() { | |
| 161 CHECK_NE(NULL, CcTest::i_isolate()->thread_local_top()); | |
| 162 return CcTest::i_isolate()->js_entry_sp(); | |
| 163 } | |
| 164 | |
| 165 | |
| 166 void TraceExtension::JSEntrySP( | |
| 167 const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 168 CHECK_NE(0, GetJsEntrySp()); | |
| 169 } | |
| 170 | |
| 171 | |
| 172 void 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 | |
| 182 static TraceExtension kTraceExtension; | |
| 183 v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension); | |
| 184 | |
| 185 | |
| 186 static bool IsAddressWithinFuncCode(JSFunction* function, Address addr) { | 59 static bool IsAddressWithinFuncCode(JSFunction* function, Address addr) { |
| 187 i::Code* code = function->code(); | 60 i::Code* code = function->code(); |
| 188 return code->contains(addr); | 61 return code->contains(addr); |
| 189 } | 62 } |
| 190 | 63 |
| 191 | 64 |
| 192 static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context, | 65 static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context, |
| 193 const char* func_name, | 66 const char* func_name, |
| 194 Address addr) { | 67 Address addr) { |
| 195 v8::Local<v8::Value> func = context->Global()->Get(v8_str(func_name)); | 68 v8::Local<v8::Value> func = context->Global()->Get(v8_str(func_name)); |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 264 | 137 |
| 265 // This test verifies that stack tracing works when called during | 138 // This test verifies that stack tracing works when called during |
| 266 // execution of a native function called from JS code. In this case, | 139 // 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 | 140 // TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack |
| 268 // walking. | 141 // walking. |
| 269 TEST(CFromJSStackTrace) { | 142 TEST(CFromJSStackTrace) { |
| 270 // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test. | 143 // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test. |
| 271 i::FLAG_use_inlining = false; | 144 i::FLAG_use_inlining = false; |
| 272 | 145 |
| 273 TickSample sample; | 146 TickSample sample; |
| 274 InitTraceEnv(&sample); | 147 i::TraceExtension::InitTraceEnv(&sample); |
| 275 | 148 |
| 276 v8::HandleScope scope(CcTest::isolate()); | 149 v8::HandleScope scope(CcTest::isolate()); |
| 277 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); | 150 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); |
| 278 v8::Context::Scope context_scope(context); | 151 v8::Context::Scope context_scope(context); |
| 279 | 152 |
| 280 // Create global function JSFuncDoTrace which calls | 153 // Create global function JSFuncDoTrace which calls |
| 281 // extension function trace() with the current frame pointer value. | 154 // extension function trace() with the current frame pointer value. |
| 282 CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace"); | 155 CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace"); |
| 283 Local<Value> result = CompileRun( | 156 Local<Value> result = CompileRun( |
| 284 "function JSTrace() {" | 157 "function JSTrace() {" |
| 285 " JSFuncDoTrace();" | 158 " JSFuncDoTrace();" |
| 286 "};\n" | 159 "};\n" |
| 287 "JSTrace();\n" | 160 "JSTrace();\n" |
| 288 "true;"); | 161 "true;"); |
| 289 CHECK(!result.IsEmpty()); | 162 CHECK(!result.IsEmpty()); |
| 290 // When stack tracer is invoked, the stack should look as follows: | 163 // When stack tracer is invoked, the stack should look as follows: |
| 291 // script [JS] | 164 // script [JS] |
| 292 // JSTrace() [JS] | 165 // JSTrace() [JS] |
| 293 // JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi] | 166 // JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi] |
| 294 // trace(EBP) [native (extension)] | 167 // trace(EBP) [native (extension)] |
| 295 // DoTrace(EBP) [native] | 168 // DoTrace(EBP) [native] |
| 296 // TickSample::Trace | 169 // TickSample::Trace |
| 297 | 170 |
| 298 CHECK(sample.has_external_callback); | 171 CHECK(sample.has_external_callback); |
| 299 CHECK_EQ(FUNCTION_ADDR(TraceExtension::Trace), sample.external_callback); | 172 CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace), sample.external_callback); |
| 300 | 173 |
| 301 // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace" | 174 // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace" |
| 302 int base = 0; | 175 int base = 0; |
| 303 CHECK_GT(sample.frames_count, base + 1); | 176 CHECK_GT(sample.frames_count, base + 1); |
| 304 | 177 |
| 305 CHECK(IsAddressWithinFuncCode( | 178 CHECK(IsAddressWithinFuncCode( |
| 306 context, "JSFuncDoTrace", sample.stack[base + 0])); | 179 context, "JSFuncDoTrace", sample.stack[base + 0])); |
| 307 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1])); | 180 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1])); |
| 308 } | 181 } |
| 309 | 182 |
| 310 | 183 |
| 311 // This test verifies that stack tracing works when called during | 184 // This test verifies that stack tracing works when called during |
| 312 // execution of JS code. However, as calling TickSample::Trace requires | 185 // execution of JS code. However, as calling TickSample::Trace requires |
| 313 // entering native code, we can only emulate pure JS by erasing | 186 // 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 | 187 // Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame |
| 315 // pointer value as a starting point for stack walking. | 188 // pointer value as a starting point for stack walking. |
| 316 TEST(PureJSStackTrace) { | 189 TEST(PureJSStackTrace) { |
| 317 // This test does not pass with inlining enabled since inlined functions | 190 // This test does not pass with inlining enabled since inlined functions |
| 318 // don't appear in the stack trace. | 191 // don't appear in the stack trace. |
| 319 i::FLAG_use_inlining = false; | 192 i::FLAG_use_inlining = false; |
| 320 | 193 |
| 321 TickSample sample; | 194 TickSample sample; |
| 322 InitTraceEnv(&sample); | 195 i::TraceExtension::InitTraceEnv(&sample); |
| 323 | 196 |
| 324 v8::HandleScope scope(CcTest::isolate()); | 197 v8::HandleScope scope(CcTest::isolate()); |
| 325 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); | 198 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); |
| 326 v8::Context::Scope context_scope(context); | 199 v8::Context::Scope context_scope(context); |
| 327 | 200 |
| 328 // Create global function JSFuncDoTrace which calls | 201 // Create global function JSFuncDoTrace which calls |
| 329 // extension function js_trace() with the current frame pointer value. | 202 // extension function js_trace() with the current frame pointer value. |
| 330 CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace"); | 203 CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace"); |
| 331 Local<Value> result = CompileRun( | 204 Local<Value> result = CompileRun( |
| 332 "function JSTrace() {" | 205 "function JSTrace() {" |
| 333 " JSFuncDoTrace();" | 206 " JSFuncDoTrace();" |
| 334 "};\n" | 207 "};\n" |
| 335 "function OuterJSTrace() {" | 208 "function OuterJSTrace() {" |
| 336 " JSTrace();" | 209 " JSTrace();" |
| 337 "};\n" | 210 "};\n" |
| 338 "OuterJSTrace();\n" | 211 "OuterJSTrace();\n" |
| 339 "true;"); | 212 "true;"); |
| 340 CHECK(!result.IsEmpty()); | 213 CHECK(!result.IsEmpty()); |
| 341 // When stack tracer is invoked, the stack should look as follows: | 214 // When stack tracer is invoked, the stack should look as follows: |
| 342 // script [JS] | 215 // script [JS] |
| 343 // OuterJSTrace() [JS] | 216 // OuterJSTrace() [JS] |
| 344 // JSTrace() [JS] | 217 // JSTrace() [JS] |
| 345 // JSFuncDoTrace() [JS] | 218 // JSFuncDoTrace() [JS] |
| 346 // js_trace(EBP) [native (extension)] | 219 // js_trace(EBP) [native (extension)] |
| 347 // DoTraceHideCEntryFPAddress(EBP) [native] | 220 // DoTraceHideCEntryFPAddress(EBP) [native] |
| 348 // TickSample::Trace | 221 // TickSample::Trace |
| 349 // | 222 // |
| 350 | 223 |
| 351 CHECK(sample.has_external_callback); | 224 CHECK(sample.has_external_callback); |
| 352 CHECK_EQ(FUNCTION_ADDR(TraceExtension::JSTrace), sample.external_callback); | 225 CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace), sample.external_callback); |
| 353 | 226 |
| 354 // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace" | 227 // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace" |
| 355 int base = 0; | 228 int base = 0; |
| 356 CHECK_GT(sample.frames_count, base + 1); | 229 CHECK_GT(sample.frames_count, base + 1); |
| 357 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0])); | 230 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0])); |
| 358 CHECK(IsAddressWithinFuncCode( | 231 CHECK(IsAddressWithinFuncCode( |
| 359 context, "OuterJSTrace", sample.stack[base + 1])); | 232 context, "OuterJSTrace", sample.stack[base + 1])); |
| 360 } | 233 } |
| 361 | 234 |
| 362 | 235 |
| 363 static void CFuncDoTrace(byte dummy_parameter) { | 236 static void CFuncDoTrace(byte dummy_parameter) { |
| 364 Address fp; | 237 Address fp; |
| 365 #ifdef __GNUC__ | 238 #ifdef __GNUC__ |
| 366 fp = reinterpret_cast<Address>(__builtin_frame_address(0)); | 239 fp = reinterpret_cast<Address>(__builtin_frame_address(0)); |
| 367 #elif defined _MSC_VER | 240 #elif defined _MSC_VER |
| 368 // Approximate a frame pointer address. We compile without base pointers, | 241 // Approximate a frame pointer address. We compile without base pointers, |
| 369 // so we can't trust ebp/rbp. | 242 // so we can't trust ebp/rbp. |
| 370 fp = &dummy_parameter - 2 * sizeof(void*); // NOLINT | 243 fp = &dummy_parameter - 2 * sizeof(void*); // NOLINT |
| 371 #else | 244 #else |
| 372 #error Unexpected platform. | 245 #error Unexpected platform. |
| 373 #endif | 246 #endif |
| 374 DoTrace(fp); | 247 i::TraceExtension::DoTrace(fp); |
| 375 } | 248 } |
| 376 | 249 |
| 377 | 250 |
| 378 static int CFunc(int depth) { | 251 static int CFunc(int depth) { |
| 379 if (depth <= 0) { | 252 if (depth <= 0) { |
| 380 CFuncDoTrace(0); | 253 CFuncDoTrace(0); |
| 381 return 0; | 254 return 0; |
| 382 } else { | 255 } else { |
| 383 return CFunc(depth - 1) + 1; | 256 return CFunc(depth - 1) + 1; |
| 384 } | 257 } |
| 385 } | 258 } |
| 386 | 259 |
| 387 | 260 |
| 388 // This test verifies that stack tracing doesn't crash when called on | 261 // 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 | 262 // pure native code. TickSample::Trace only unrolls JS code, so we can't |
| 390 // get any meaningful info here. | 263 // get any meaningful info here. |
| 391 TEST(PureCStackTrace) { | 264 TEST(PureCStackTrace) { |
| 392 TickSample sample; | 265 TickSample sample; |
| 393 InitTraceEnv(&sample); | 266 i::TraceExtension::InitTraceEnv(&sample); |
| 394 v8::HandleScope scope(CcTest::isolate()); | 267 v8::HandleScope scope(CcTest::isolate()); |
| 395 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); | 268 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); |
| 396 v8::Context::Scope context_scope(context); | 269 v8::Context::Scope context_scope(context); |
| 397 // Check that sampler doesn't crash | 270 // Check that sampler doesn't crash |
| 398 CHECK_EQ(10, CFunc(10)); | 271 CHECK_EQ(10, CFunc(10)); |
| 399 } | 272 } |
| 400 | 273 |
| 401 | 274 |
| 402 TEST(JsEntrySp) { | 275 TEST(JsEntrySp) { |
| 403 v8::HandleScope scope(CcTest::isolate()); | 276 v8::HandleScope scope(CcTest::isolate()); |
| 404 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); | 277 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); |
| 405 v8::Context::Scope context_scope(context); | 278 v8::Context::Scope context_scope(context); |
| 406 CHECK_EQ(0, GetJsEntrySp()); | 279 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); |
| 407 CompileRun("a = 1; b = a + 1;"); | 280 CompileRun("a = 1; b = a + 1;"); |
| 408 CHECK_EQ(0, GetJsEntrySp()); | 281 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); |
| 409 CompileRun("js_entry_sp();"); | 282 CompileRun("js_entry_sp();"); |
| 410 CHECK_EQ(0, GetJsEntrySp()); | 283 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); |
| 411 CompileRun("js_entry_sp_level2();"); | 284 CompileRun("js_entry_sp_level2();"); |
| 412 CHECK_EQ(0, GetJsEntrySp()); | 285 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); |
| 413 } | 286 } |
| OLD | NEW |