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