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