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 |