test-log-stack-tracer.cc 11.3 KB
Newer Older
1
// Copyright 2011 the V8 project authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 28 29 30 31
//
// Tests of profiler-related functions from log.h

#include <stdlib.h>

32
#include "include/v8-profiler.h"
33
#include "src/api/api-inl.h"
34
#include "src/diagnostics/disassembler.h"
35
#include "src/execution/frames.h"
36 37
#include "src/execution/isolate.h"
#include "src/execution/vm-state-inl.h"
38
#include "src/init/v8.h"
39
#include "src/objects/objects-inl.h"
40
#include "src/profiler/tick-sample.h"
41 42
#include "test/cctest/cctest.h"
#include "test/cctest/trace-extension.h"
43

44 45
namespace v8 {
namespace internal {
46

47 48 49
static bool IsAddressWithinFuncCode(JSFunction function, Isolate* isolate,
                                    void* addr) {
  i::AbstractCode code = function.abstract_code(isolate);
50
  return code.contains(reinterpret_cast<Address>(addr));
51 52
}

53
static bool IsAddressWithinFuncCode(v8::Local<v8::Context> context,
54 55
                                    Isolate* isolate, const char* func_name,
                                    void* addr) {
56 57
  v8::Local<v8::Value> func =
      context->Global()->Get(context, v8_str(func_name)).ToLocalChecked();
58
  CHECK(func->IsFunction());
59
  JSFunction js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func));
60
  return IsAddressWithinFuncCode(js_func, isolate, addr);
61 62
}

63 64 65
// This C++ function is called as a constructor, to grab the frame pointer
// from the calling function.  When this function runs, the stack contains
// a C_Entry frame and a Construct frame above the calling function's frame.
66
static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) {
67 68
  i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
  i::StackFrameIterator frame_iterator(isolate);
69 70
  CHECK(frame_iterator.frame()->is_exit() ||
        frame_iterator.frame()->is_builtin_exit());
71 72 73
  frame_iterator.Advance();
  CHECK(frame_iterator.frame()->is_construct());
  frame_iterator.Advance();
74
  if (frame_iterator.frame()->type() == i::StackFrame::STUB) {
75 76 77
    // Skip over bytecode handler frame.
    frame_iterator.Advance();
  }
78 79 80
  i::StackFrame* calling_frame = frame_iterator.frame();
  CHECK(calling_frame->is_java_script());

81
  v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
82
#if defined(V8_HOST_ARCH_32_BIT)
83
  int32_t low_bits = static_cast<int32_t>(calling_frame->fp());
84 85 86
  args.This()
      ->Set(context, v8_str("low_bits"), v8_num(low_bits >> 1))
      .FromJust();
87
#elif defined(V8_HOST_ARCH_64_BIT)
88
  Address fp = calling_frame->fp();
89 90 91 92 93 94 95 96 97
  uint64_t kSmiValueMask =
      (static_cast<uintptr_t>(1) << (kSmiValueSize - 1)) - 1;
  int32_t low_bits = static_cast<int32_t>(fp & kSmiValueMask);
  fp >>= kSmiValueSize - 1;
  int32_t high_bits = static_cast<int32_t>(fp & kSmiValueMask);
  fp >>= kSmiValueSize - 1;
  CHECK_EQ(fp, 0);  // Ensure all the bits are successfully encoded.
  args.This()->Set(context, v8_str("low_bits"), v8_int(low_bits)).FromJust();
  args.This()->Set(context, v8_str("high_bits"), v8_int(high_bits)).FromJust();
98 99 100
#else
#error Host architecture is neither 32-bit nor 64-bit.
#endif
101
  args.GetReturnValue().Set(args.This());
102
}
103 104


105
// Use the API to create a JSFunction object that calls the above C++ function.
106 107
void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context,
                                          const char* constructor_name) {
108
    Local<v8::FunctionTemplate> constructor_template =
109
        v8::FunctionTemplate::New(context->GetIsolate(), construct_call);
110
    constructor_template->SetClassName(v8_str("FPGrabber"));
111 112 113
    Local<Function> fun =
        constructor_template->GetFunction(context).ToLocalChecked();
    context->Global()->Set(context, v8_str(constructor_name), fun).FromJust();
114
}
115 116 117 118


// Creates a global function named 'func_name' that calls the tracing
// function 'trace_func_name' with an actual EBP register value,
119
// encoded as one or two Smis.
120 121
static void CreateTraceCallerFunction(v8::Local<v8::Context> context,
                                      const char* func_name,
122 123
                                      const char* trace_func_name) {
  i::EmbeddedVector<char, 256> trace_call_buf;
124 125 126 127 128 129
  i::SNPrintF(trace_call_buf,
              "function %s() {"
              "  fp = new FPGrabber();"
              "  %s(fp.low_bits, fp.high_bits);"
              "}",
              func_name, trace_func_name);
130 131 132

  // Create the FPGrabber function, which grabs the caller's frame pointer
  // when called as a constructor.
133
  CreateFramePointerGrabberConstructor(context, "FPGrabber");
134 135

  // Compile the script.
136
  CompileRun(trace_call_buf.begin());
137 138 139
}


140 141
// This test verifies that stack tracing works when called during
// execution of a native function called from JS code. In this case,
142
// TickSample::Trace uses Isolate::c_entry_fp as a starting point for stack
143
// walking.
144
TEST(CFromJSStackTrace) {
145
  // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test.
146
  i::FLAG_turbo_inlining = false;
147

148
  TickSample sample;
149
  i::TraceExtension::InitTraceEnv(&sample);
150

151
  v8::HandleScope scope(CcTest::isolate());
152
  v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
153 154
  v8::Context::Scope context_scope(context);

155 156
  // Create global function JSFuncDoTrace which calls
  // extension function trace() with the current frame pointer value.
157
  CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace");
158
  Local<Value> result = CompileRun(
159 160 161
      "function JSTrace() {"
      "         JSFuncDoTrace();"
      "};\n"
162 163 164
      "JSTrace();\n"
      "true;");
  CHECK(!result.IsEmpty());
165 166 167 168
  // When stack tracer is invoked, the stack should look as follows:
  // script [JS]
  //   JSTrace() [JS]
  //     JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
169
  //       trace(EBP) [native (extension)]
170
  //         DoTrace(EBP) [native]
171
  //           TickSample::Trace
172

173
  CHECK(sample.has_external_callback);
174
  CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace),
175
           reinterpret_cast<Address>(sample.external_callback_entry));
176

177
  // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
178
  unsigned base = 0;
179
  CHECK_GT(sample.frames_count, base + 1);
180

181 182 183 184
  CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "JSFuncDoTrace",
                                sample.stack[base + 0]));
  CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "JSTrace",
                                sample.stack[base + 1]));
185 186 187
}


188
// This test verifies that stack tracing works when called during
189
// execution of JS code. However, as calling TickSample::Trace requires
190
// entering native code, we can only emulate pure JS by erasing
191
// Isolate::c_entry_fp value. In this case, TickSample::Trace uses passed frame
192
// pointer value as a starting point for stack walking.
193
TEST(PureJSStackTrace) {
194 195
  // This test does not pass with inlining enabled since inlined functions
  // don't appear in the stack trace.
196
  i::FLAG_turbo_inlining = false;
197

198
  TickSample sample;
199
  i::TraceExtension::InitTraceEnv(&sample);
200

201
  v8::HandleScope scope(CcTest::isolate());
202
  v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
203 204
  v8::Context::Scope context_scope(context);

205 206
  // Create global function JSFuncDoTrace which calls
  // extension function js_trace() with the current frame pointer value.
207
  CreateTraceCallerFunction(context, "JSFuncDoTrace", "js_trace");
208
  Local<Value> result = CompileRun(
209 210 211 212 213 214
      "function JSTrace() {"
      "         JSFuncDoTrace();"
      "};\n"
      "function OuterJSTrace() {"
      "         JSTrace();"
      "};\n"
215 216 217
      "OuterJSTrace();\n"
      "true;");
  CHECK(!result.IsEmpty());
218 219 220 221
  // When stack tracer is invoked, the stack should look as follows:
  // script [JS]
  //   OuterJSTrace() [JS]
  //     JSTrace() [JS]
222 223
  //       JSFuncDoTrace() [JS]
  //         js_trace(EBP) [native (extension)]
224
  //           DoTraceHideCEntryFPAddress(EBP) [native]
225
  //             TickSample::Trace
226
  //
227

228
  CHECK(sample.has_external_callback);
229
  CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace),
230
           reinterpret_cast<Address>(sample.external_callback_entry));
231

232
  // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
233
  unsigned base = 0;
234
  CHECK_GT(sample.frames_count, base + 1);
235 236 237 238
  CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "JSTrace",
                                sample.stack[base + 0]));
  CHECK(IsAddressWithinFuncCode(context, CcTest::i_isolate(), "OuterJSTrace",
                                sample.stack[base + 1]));
239 240
}

241
static void CFuncDoTrace(byte dummy_param) {
242
  Address fp;
243
#if V8_HAS_BUILTIN_FRAME_ADDRESS
244
  fp = reinterpret_cast<Address>(__builtin_frame_address(0));
245
#elif V8_CC_MSVC
246 247
  // Approximate a frame pointer address. We compile without base pointers,
  // so we can't trust ebp/rbp.
248
  fp = reinterpret_cast<Address>(&dummy_param) - 2 * sizeof(void*);  // NOLINT
249 250
#else
#error Unexpected platform.
251
#endif
252
  i::TraceExtension::DoTrace(fp);
253 254 255 256 257
}


static int CFunc(int depth) {
  if (depth <= 0) {
258
    CFuncDoTrace(0);
259 260 261 262 263 264 265
    return 0;
  } else {
    return CFunc(depth - 1) + 1;
  }
}


266
// This test verifies that stack tracing doesn't crash when called on
267
// pure native code. TickSample::Trace only unrolls JS code, so we can't
268
// get any meaningful info here.
269 270
TEST(PureCStackTrace) {
  TickSample sample;
271
  i::TraceExtension::InitTraceEnv(&sample);
272
  v8::HandleScope scope(CcTest::isolate());
273
  v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
274
  v8::Context::Scope context_scope(context);
275 276 277 278 279
  // Check that sampler doesn't crash
  CHECK_EQ(10, CFunc(10));
}


280
TEST(JsEntrySp) {
281
  v8::HandleScope scope(CcTest::isolate());
282
  v8::Local<v8::Context> context = CcTest::NewContext({TRACE_EXTENSION_ID});
283
  v8::Context::Scope context_scope(context);
284
  CHECK(!i::TraceExtension::GetJsEntrySp());
285
  CompileRun("a = 1; b = a + 1;");
286
  CHECK(!i::TraceExtension::GetJsEntrySp());
287
  CompileRun("js_entry_sp();");
288
  CHECK(!i::TraceExtension::GetJsEntrySp());
289
  CompileRun("js_entry_sp_level2();");
290
  CHECK(!i::TraceExtension::GetJsEntrySp());
291
}
292 293 294

}  // namespace internal
}  // namespace v8