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