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