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
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.
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.
28 // Tests of profiler-related functions from log.h
37 #include "disassembler.h"
41 #include "vm-state-inl.h"
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;
61 } trace_env = { NULL };
64 static void InitTraceEnv(TickSample* sample) {
65 trace_env.sample = sample;
69 static void DoTrace(Address fp) {
72 // sp is only used to define stack high bound
74 reinterpret_cast<Address>(trace_env.sample) - 10240;
75 trace_env.sample->Init(CcTest::i_isolate(), regs);
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;
87 *(CcTest::i_isolate()->c_entry_fp_address()) = saved_c_frame_fp;
91 // --- T r a c e E x t e n s i o n ---
93 class TraceExtension : public v8::Extension {
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);
103 static Address GetFP(const v8::FunctionCallbackInfo<v8::Value>& args);
104 static const char* kSource;
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();";
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);
126 return v8::Handle<v8::FunctionTemplate>();
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);
141 #error Host architecture is neither 32-bit nor 64-bit.
143 printf("Trace: %p\n", fp);
148 void TraceExtension::Trace(const v8::FunctionCallbackInfo<v8::Value>& args) {
149 DoTrace(GetFP(args));
153 void TraceExtension::JSTrace(const v8::FunctionCallbackInfo<v8::Value>& args) {
154 DoTraceHideCEntryFPAddress(GetFP(args));
158 static Address GetJsEntrySp() {
159 CHECK_NE(NULL, CcTest::i_isolate()->thread_local_top());
160 return CcTest::i_isolate()->js_entry_sp();
164 void TraceExtension::JSEntrySP(
165 const v8::FunctionCallbackInfo<v8::Value>& args) {
166 CHECK_NE(0, GetJsEntrySp());
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());
180 static TraceExtension kTraceExtension;
181 v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension);
184 static bool IsAddressWithinFuncCode(JSFunction* function, Address addr) {
185 i::Code* code = function->code();
186 return code->contains(addr);
190 static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context,
191 const char* func_name,
193 v8::Local<v8::Value> func = context->Global()->Get(v8_str(func_name));
194 CHECK(func->IsFunction());
195 JSFunction* js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
196 return IsAddressWithinFuncCode(js_func, addr);
200 // This C++ function is called as a constructor, to grab the frame pointer
201 // from the calling function. When this function runs, the stack contains
202 // a C_Entry frame and a Construct frame above the calling function's frame.
203 static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) {
204 i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
205 i::StackFrameIterator frame_iterator(isolate);
206 CHECK(frame_iterator.frame()->is_exit());
207 frame_iterator.Advance();
208 CHECK(frame_iterator.frame()->is_construct());
209 frame_iterator.Advance();
210 i::StackFrame* calling_frame = frame_iterator.frame();
211 CHECK(calling_frame->is_java_script());
213 #if defined(V8_HOST_ARCH_32_BIT)
214 int32_t low_bits = reinterpret_cast<int32_t>(calling_frame->fp());
215 args.This()->Set(v8_str("low_bits"), v8_num(low_bits >> 1));
216 #elif defined(V8_HOST_ARCH_64_BIT)
217 uint64_t fp = reinterpret_cast<uint64_t>(calling_frame->fp());
218 int32_t low_bits = static_cast<int32_t>(fp & 0xffffffff);
219 int32_t high_bits = static_cast<int32_t>(fp >> 32);
220 args.This()->Set(v8_str("low_bits"), v8_num(low_bits));
221 args.This()->Set(v8_str("high_bits"), v8_num(high_bits));
223 #error Host architecture is neither 32-bit nor 64-bit.
225 args.GetReturnValue().Set(args.This());
229 // Use the API to create a JSFunction object that calls the above C++ function.
230 void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context,
231 const char* constructor_name) {
232 Local<v8::FunctionTemplate> constructor_template =
233 v8::FunctionTemplate::New(construct_call);
234 constructor_template->SetClassName(v8_str("FPGrabber"));
235 Local<Function> fun = constructor_template->GetFunction();
236 context->Global()->Set(v8_str(constructor_name), fun);
240 // Creates a global function named 'func_name' that calls the tracing
241 // function 'trace_func_name' with an actual EBP register value,
242 // encoded as one or two Smis.
243 static void CreateTraceCallerFunction(v8::Local<v8::Context> context,
244 const char* func_name,
245 const char* trace_func_name) {
246 i::EmbeddedVector<char, 256> trace_call_buf;
247 i::OS::SNPrintF(trace_call_buf,
249 " fp = new FPGrabber();"
250 " %s(fp.low_bits, fp.high_bits);"
252 func_name, trace_func_name);
254 // Create the FPGrabber function, which grabs the caller's frame pointer
255 // when called as a constructor.
256 CreateFramePointerGrabberConstructor(context, "FPGrabber");
258 // Compile the script.
259 CompileRun(trace_call_buf.start());
263 // This test verifies that stack tracing works when called during
264 // execution of a native function called from JS code. In this case,
265 // TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack
267 TEST(CFromJSStackTrace) {
268 // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
269 i::FLAG_use_inlining = false;
272 InitTraceEnv(&sample);
274 v8::HandleScope scope(CcTest::isolate());
275 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
276 v8::Context::Scope context_scope(context);
278 // Create global function JSFuncDoTrace which calls
279 // extension function trace() with the current frame pointer value.
280 CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace");
281 Local<Value> result = CompileRun(
282 "function JSTrace() {"
287 CHECK(!result.IsEmpty());
288 // When stack tracer is invoked, the stack should look as follows:
291 // JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
292 // trace(EBP) [native (extension)]
293 // DoTrace(EBP) [native]
296 CHECK(sample.has_external_callback);
297 CHECK_EQ(FUNCTION_ADDR(TraceExtension::Trace), sample.external_callback);
299 // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
301 CHECK_GT(sample.frames_count, base + 1);
303 CHECK(IsAddressWithinFuncCode(
304 context, "JSFuncDoTrace", sample.stack[base + 0]));
305 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1]));
309 // This test verifies that stack tracing works when called during
310 // execution of JS code. However, as calling TickSample::Trace requires
311 // entering native code, we can only emulate pure JS by erasing
312 // Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame
313 // pointer value as a starting point for stack walking.
314 TEST(PureJSStackTrace) {
315 // This test does not pass with inlining enabled since inlined functions
316 // don't appear in the stack trace.
317 i::FLAG_use_inlining = false;
320 InitTraceEnv(&sample);
322 v8::HandleScope scope(CcTest::isolate());
323 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
324 v8::Context::Scope context_scope(context);
326 // Create global function JSFuncDoTrace which calls
327 // extension function js_trace() with the current frame pointer value.
328 CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace");
329 Local<Value> result = CompileRun(
330 "function JSTrace() {"
333 "function OuterJSTrace() {"
338 CHECK(!result.IsEmpty());
339 // When stack tracer is invoked, the stack should look as follows:
341 // OuterJSTrace() [JS]
343 // JSFuncDoTrace() [JS]
344 // js_trace(EBP) [native (extension)]
345 // DoTraceHideCEntryFPAddress(EBP) [native]
349 CHECK(sample.has_external_callback);
350 CHECK_EQ(FUNCTION_ADDR(TraceExtension::JSTrace), sample.external_callback);
352 // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
354 CHECK_GT(sample.frames_count, base + 1);
355 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0]));
356 CHECK(IsAddressWithinFuncCode(
357 context, "OuterJSTrace", sample.stack[base + 1]));
361 static void CFuncDoTrace(byte dummy_parameter) {
364 fp = reinterpret_cast<Address>(__builtin_frame_address(0));
365 #elif defined _MSC_VER
366 // Approximate a frame pointer address. We compile without base pointers,
367 // so we can't trust ebp/rbp.
368 fp = &dummy_parameter - 2 * sizeof(void*); // NOLINT
370 #error Unexpected platform.
376 static int CFunc(int depth) {
381 return CFunc(depth - 1) + 1;
386 // This test verifies that stack tracing doesn't crash when called on
387 // pure native code. TickSample::Trace only unrolls JS code, so we can't
388 // get any meaningful info here.
389 TEST(PureCStackTrace) {
391 InitTraceEnv(&sample);
392 v8::HandleScope scope(CcTest::isolate());
393 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
394 v8::Context::Scope context_scope(context);
395 // Check that sampler doesn't crash
396 CHECK_EQ(10, CFunc(10));
401 v8::HandleScope scope(CcTest::isolate());
402 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
403 v8::Context::Scope context_scope(context);
404 CHECK_EQ(0, GetJsEntrySp());
405 CompileRun("a = 1; b = a + 1;");
406 CHECK_EQ(0, GetJsEntrySp());
407 CompileRun("js_entry_sp();");
408 CHECK_EQ(0, GetJsEntrySp());
409 CompileRun("js_entry_sp_level2();");
410 CHECK_EQ(0, GetJsEntrySp());