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/sampler.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::Code* code = function->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 = context->Global()->Get(v8_str(func_name)); 69 CHECK(func->IsFunction()); 70 JSFunction* js_func = JSFunction::cast(*v8::Utils::OpenHandle(*func)); 71 return IsAddressWithinFuncCode(js_func, addr); 72 } 73 74 75 // This C++ function is called as a constructor, to grab the frame pointer 76 // from the calling function. When this function runs, the stack contains 77 // a C_Entry frame and a Construct frame above the calling function's frame. 78 static void construct_call(const v8::FunctionCallbackInfo<v8::Value>& args) { 79 i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate()); 80 i::StackFrameIterator frame_iterator(isolate); 81 CHECK(frame_iterator.frame()->is_exit()); 82 frame_iterator.Advance(); 83 CHECK(frame_iterator.frame()->is_construct()); 84 frame_iterator.Advance(); 85 i::StackFrame* calling_frame = frame_iterator.frame(); 86 CHECK(calling_frame->is_java_script()); 87 88 #if defined(V8_HOST_ARCH_32_BIT) 89 int32_t low_bits = reinterpret_cast<int32_t>(calling_frame->fp()); 90 args.This()->Set(v8_str("low_bits"), v8_num(low_bits >> 1)); 91 #elif defined(V8_HOST_ARCH_64_BIT) 92 uint64_t fp = reinterpret_cast<uint64_t>(calling_frame->fp()); 93 int32_t low_bits = static_cast<int32_t>(fp & 0xffffffff); 94 int32_t high_bits = static_cast<int32_t>(fp >> 32); 95 args.This()->Set(v8_str("low_bits"), v8_num(low_bits)); 96 args.This()->Set(v8_str("high_bits"), v8_num(high_bits)); 97 #else 98 #error Host architecture is neither 32-bit nor 64-bit. 99 #endif 100 args.GetReturnValue().Set(args.This()); 101 } 102 103 104 // Use the API to create a JSFunction object that calls the above C++ function. 105 void CreateFramePointerGrabberConstructor(v8::Local<v8::Context> context, 106 const char* constructor_name) { 107 Local<v8::FunctionTemplate> constructor_template = 108 v8::FunctionTemplate::New(context->GetIsolate(), construct_call); 109 constructor_template->SetClassName(v8_str("FPGrabber")); 110 Local<Function> fun = constructor_template->GetFunction(); 111 context->Global()->Set(v8_str(constructor_name), fun); 112 } 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 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 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 CreateFramePointerGrabberConstructor(context, "FPGrabber"); 132 133 // Compile the script. 134 CompileRun(trace_call_buf.start()); 135 } 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 TEST(CFromJSStackTrace) { 143 // BUG(1303) Inlining of JSFuncDoTrace() in JSTrace below breaks this test. 144 i::FLAG_use_inlining = false; 145 146 TickSample sample; 147 i::TraceExtension::InitTraceEnv(&sample); 148 149 v8::HandleScope scope(CcTest::isolate()); 150 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); 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 CreateTraceCallerFunction(context, "JSFuncDoTrace", "trace"); 156 Local<Value> result = CompileRun( 157 "function JSTrace() {" 158 " JSFuncDoTrace();" 159 "};\n" 160 "JSTrace();\n" 161 "true;"); 162 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 CHECK(sample.has_external_callback); 172 CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::Trace), sample.external_callback); 173 174 // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace" 175 int base = 0; 176 CHECK_GT(sample.frames_count, base + 1); 177 178 CHECK(IsAddressWithinFuncCode( 179 context, "JSFuncDoTrace", sample.stack[base + 0])); 180 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 1])); 181 } 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 TEST(PureJSStackTrace) { 190 // This test does not pass with inlining enabled since inlined functions 191 // don't appear in the stack trace. 192 i::FLAG_use_inlining = false; 193 194 TickSample sample; 195 i::TraceExtension::InitTraceEnv(&sample); 196 197 v8::HandleScope scope(CcTest::isolate()); 198 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); 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 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 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 CHECK(sample.has_external_callback); 225 CHECK_EQ(FUNCTION_ADDR(i::TraceExtension::JSTrace), sample.external_callback); 226 227 // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace" 228 int base = 0; 229 CHECK_GT(sample.frames_count, base + 1); 230 CHECK(IsAddressWithinFuncCode(context, "JSTrace", sample.stack[base + 0])); 231 CHECK(IsAddressWithinFuncCode( 232 context, "OuterJSTrace", sample.stack[base + 1])); 233 } 234 235 236 static void CFuncDoTrace(byte dummy_parameter) { 237 Address fp; 238 #ifdef __GNUC__ 239 fp = reinterpret_cast<Address>(__builtin_frame_address(0)); 240 #elif defined _MSC_VER 241 // Approximate a frame pointer address. We compile without base pointers, 242 // so we can't trust ebp/rbp. 243 fp = &dummy_parameter - 2 * sizeof(void*); // NOLINT 244 #else 245 #error Unexpected platform. 246 #endif 247 i::TraceExtension::DoTrace(fp); 248 } 249 250 251 static int CFunc(int depth) { 252 if (depth <= 0) { 253 CFuncDoTrace(0); 254 return 0; 255 } else { 256 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 TEST(PureCStackTrace) { 265 TickSample sample; 266 i::TraceExtension::InitTraceEnv(&sample); 267 v8::HandleScope scope(CcTest::isolate()); 268 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); 269 v8::Context::Scope context_scope(context); 270 // Check that sampler doesn't crash 271 CHECK_EQ(10, CFunc(10)); 272 } 273 274 275 TEST(JsEntrySp) { 276 v8::HandleScope scope(CcTest::isolate()); 277 v8::Local<v8::Context> context = CcTest::NewContext(TRACE_EXTENSION); 278 v8::Context::Scope context_scope(context); 279 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); 280 CompileRun("a = 1; b = a + 1;"); 281 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); 282 CompileRun("js_entry_sp();"); 283 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); 284 CompileRun("js_entry_sp_level2();"); 285 CHECK_EQ(0, i::TraceExtension::GetJsEntrySp()); 286 } 287