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 "src/v8.h"
33
34#include "src/api.h"
35#include "src/codegen.h"
36#include "src/disassembler.h"
37#include "src/isolate.h"
38#include "src/log.h"
39#include "src/sampler.h"
40#include "src/vm-state-inl.h"
41#include "test/cctest/cctest.h"
42#include "test/cctest/trace-extension.h"
43
44using v8::Function;
45using v8::Local;
46using v8::Object;
47using v8::Script;
48using v8::String;
49using v8::Value;
50
51using v8::internal::byte;
52using v8::internal::Address;
53using v8::internal::Handle;
54using v8::internal::Isolate;
55using v8::internal::JSFunction;
56using v8::internal::TickSample;
57
58
59static bool IsAddressWithinFuncCode(JSFunction* function, Address addr) {
60  i::Code* code = function->code();
61  return code->contains(addr);
62}
63
64
65static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context,
66                                    const char* func_name,
67                                    Address addr) {
68  v8::Local<v8::Value> func = context->Global()->Get(v8_str(func_name));
69  CHECK(func->IsFunction());
70  JSFunction* js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
71  return IsAddressWithinFuncCode(js_func, addr);
72}
73
74
75// This C++ function is called as a constructor, to grab the frame pointer
76// from the calling function.  When this function runs, the stack contains
77// a C_Entry frame and a Construct frame above the calling function's frame.
78static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) {
79  i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
80  i::StackFrameIterator frame_iterator(isolate);
81  CHECK(frame_iterator.frame()->is_exit());
82  frame_iterator.Advance();
83  CHECK(frame_iterator.frame()->is_construct());
84  frame_iterator.Advance();
85  i::StackFrame* calling_frame = frame_iterator.frame();
86  CHECK(calling_frame->is_java_script());
87
88#if defined(V8_HOST_ARCH_32_BIT)
89  int32_t low_bits = reinterpret_cast<int32_t>(calling_frame->fp());
90  args.This()->Set(v8_str("low_bits"), v8_num(low_bits >> 1));
91#elif defined(V8_HOST_ARCH_64_BIT)
92  uint64_t fp = reinterpret_cast<uint64_t>(calling_frame->fp());
93  int32_t low_bits = static_cast<int32_t>(fp & 0xffffffff);
94  int32_t high_bits = static_cast<int32_t>(fp >> 32);
95  args.This()->Set(v8_str("low_bits"), v8_num(low_bits));
96  args.This()->Set(v8_str("high_bits"), v8_num(high_bits));
97#else
98#error Host architecture is neither 32-bit nor 64-bit.
99#endif
100  args.GetReturnValue().Set(args.This());
101}
102
103
104// Use the API to create a JSFunction object that calls the above C++ function.
105void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context,
106                                          const char* constructor_name) {
107    Local<v8::FunctionTemplate> constructor_template =
108        v8::FunctionTemplate::New(context->GetIsolate(), construct_call);
109    constructor_template->SetClassName(v8_str("FPGrabber"));
110    Local<Function> fun = constructor_template->GetFunction();
111    context->Global()->Set(v8_str(constructor_name), fun);
112}
113
114
115// Creates a global function named 'func_name' that calls the tracing
116// function 'trace_func_name' with an actual EBP register value,
117// encoded as one or two Smis.
118static void CreateTraceCallerFunction(v8::Local<v8::Context> context,
119                                      const char* func_name,
120                                      const char* trace_func_name) {
121  i::EmbeddedVector<char, 256> trace_call_buf;
122  i::SNPrintF(trace_call_buf,
123              "function %s() {"
124              "  fp = new FPGrabber();"
125              "  %s(fp.low_bits, fp.high_bits);"
126              "}",
127              func_name, trace_func_name);
128
129  // Create the FPGrabber function, which grabs the caller's frame pointer
130  // when called as a constructor.
131  CreateFramePointerGrabberConstructor(context, "FPGrabber");
132
133  // Compile the script.
134  CompileRun(trace_call_buf.start());
135}
136
137
138// This test verifies that stack tracing works when called during
139// execution of a native function called from JS code. In this case,
140// TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack
141// walking.
142TEST(CFromJSStackTrace) {
143  // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
144  i::FLAG_use_inlining = false;
145
146  TickSample sample;
147  i::TraceExtension::InitTraceEnv(&sample);
148
149  v8::HandleScope scope(CcTest::isolate());
150  v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
151  v8::Context::Scope context_scope(context);
152
153  // Create global function JSFuncDoTrace which calls
154  // extension function trace() with the current frame pointer value.
155  CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace");
156  Local<Value> result = CompileRun(
157      "function JSTrace() {"
158      "         JSFuncDoTrace();"
159      "};\n"
160      "JSTrace();\n"
161      "true;");
162  CHECK(!result.IsEmpty());
163  // When stack tracer is invoked, the stack should look as follows:
164  // script [JS]
165  //   JSTrace() [JS]
166  //     JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
167  //       trace(EBP) [native (extension)]
168  //         DoTrace(EBP) [native]
169  //           TickSample::Trace
170
171  CHECK(sample.has_external_callback);
172  CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace), sample.external_callback);
173
174  // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
175  unsigned base = 0;
176  CHECK_GT(sample.frames_count, base + 1);
177
178  CHECK(IsAddressWithinFuncCode(
179      context, "JSFuncDoTrace", sample.stack[base + 0]));
180  CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1]));
181}
182
183
184// This test verifies that stack tracing works when called during
185// execution of JS code. However, as calling TickSample::Trace requires
186// entering native code, we can only emulate pure JS by erasing
187// Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame
188// pointer value as a starting point for stack walking.
189TEST(PureJSStackTrace) {
190  // This test does not pass with inlining enabled since inlined functions
191  // don't appear in the stack trace.
192  i::FLAG_use_inlining = false;
193
194  TickSample sample;
195  i::TraceExtension::InitTraceEnv(&sample);
196
197  v8::HandleScope scope(CcTest::isolate());
198  v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
199  v8::Context::Scope context_scope(context);
200
201  // Create global function JSFuncDoTrace which calls
202  // extension function js_trace() with the current frame pointer value.
203  CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace");
204  Local<Value> result = CompileRun(
205      "function JSTrace() {"
206      "         JSFuncDoTrace();"
207      "};\n"
208      "function OuterJSTrace() {"
209      "         JSTrace();"
210      "};\n"
211      "OuterJSTrace();\n"
212      "true;");
213  CHECK(!result.IsEmpty());
214  // When stack tracer is invoked, the stack should look as follows:
215  // script [JS]
216  //   OuterJSTrace() [JS]
217  //     JSTrace() [JS]
218  //       JSFuncDoTrace() [JS]
219  //         js_trace(EBP) [native (extension)]
220  //           DoTraceHideCEntryFPAddress(EBP) [native]
221  //             TickSample::Trace
222  //
223
224  CHECK(sample.has_external_callback);
225  CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace), sample.external_callback);
226
227  // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
228  unsigned base = 0;
229  CHECK_GT(sample.frames_count, base + 1);
230  CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0]));
231  CHECK(IsAddressWithinFuncCode(
232      context, "OuterJSTrace", sample.stack[base + 1]));
233}
234
235
236static void CFuncDoTrace(byte dummy_parameter) {
237  Address fp;
238#ifdef __GNUC__
239  fp = reinterpret_cast<Address>(__builtin_frame_address(0));
240#elif defined _MSC_VER
241  // Approximate a frame pointer address. We compile without base pointers,
242  // so we can't trust ebp/rbp.
243  fp = &dummy_parameter - 2 * sizeof(void*);  // NOLINT
244#else
245#error Unexpected platform.
246#endif
247  i::TraceExtension::DoTrace(fp);
248}
249
250
251static int CFunc(int depth) {
252  if (depth <= 0) {
253    CFuncDoTrace(0);
254    return 0;
255  } else {
256    return CFunc(depth - 1) + 1;
257  }
258}
259
260
261// This test verifies that stack tracing doesn't crash when called on
262// pure native code. TickSample::Trace only unrolls JS code, so we can't
263// get any meaningful info here.
264TEST(PureCStackTrace) {
265  TickSample sample;
266  i::TraceExtension::InitTraceEnv(&sample);
267  v8::HandleScope scope(CcTest::isolate());
268  v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
269  v8::Context::Scope context_scope(context);
270  // Check that sampler doesn't crash
271  CHECK_EQ(10, CFunc(10));
272}
273
274
275TEST(JsEntrySp) {
276  v8::HandleScope scope(CcTest::isolate());
277  v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION);
278  v8::Context::Scope context_scope(context);
279  CHECK_EQ(0, i::TraceExtension::GetJsEntrySp());
280  CompileRun("a = 1; b = a + 1;");
281  CHECK_EQ(0, i::TraceExtension::GetJsEntrySp());
282  CompileRun("js_entry_sp();");
283  CHECK_EQ(0, i::TraceExtension::GetJsEntrySp());
284  CompileRun("js_entry_sp_level2();");
285  CHECK_EQ(0, i::TraceExtension::GetJsEntrySp());
286}
287