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