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