blob: 4d9d75927d1a18e8f6d69bd8cac71d6b9126119b [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// Copyright 2006-2009 the V8 project authors. All rights reserved.
2//
3// Tests of profiler-related functions from log.h
4
5#ifdef ENABLE_LOGGING_AND_PROFILING
6
7#include <stdlib.h>
8
9#include "v8.h"
10
11#include "codegen.h"
12#include "log.h"
13#include "top.h"
14#include "cctest.h"
15#include "disassembler.h"
16#include "register-allocator-inl.h"
17
18using v8::Function;
19using v8::Local;
20using v8::Object;
21using v8::Script;
22using v8::String;
23using v8::Value;
24
25using v8::internal::byte;
26using v8::internal::Address;
27using v8::internal::Handle;
28using v8::internal::JSFunction;
29using v8::internal::StackTracer;
30using v8::internal::TickSample;
31using v8::internal::Top;
32
33namespace i = v8::internal;
34
35
36static v8::Persistent<v8::Context> env;
37
38
39static struct {
40 TickSample* sample;
41} trace_env = { NULL };
42
43
44static void InitTraceEnv(TickSample* sample) {
45 trace_env.sample = sample;
46}
47
48
49static void DoTrace(Address fp) {
Leon Clarked91b9f72010-01-27 17:25:45 +000050 trace_env.sample->fp = fp;
Steve Blocka7e24c12009-10-30 11:49:00 +000051 // sp is only used to define stack high bound
52 trace_env.sample->sp =
Leon Clarked91b9f72010-01-27 17:25:45 +000053 reinterpret_cast<Address>(trace_env.sample) - 10240;
Steve Blocka7e24c12009-10-30 11:49:00 +000054 StackTracer::Trace(trace_env.sample);
55}
56
57
58// Hide c_entry_fp to emulate situation when sampling is done while
59// pure JS code is being executed
60static void DoTraceHideCEntryFPAddress(Address fp) {
61 v8::internal::Address saved_c_frame_fp = *(Top::c_entry_fp_address());
62 CHECK(saved_c_frame_fp);
63 *(Top::c_entry_fp_address()) = 0;
64 DoTrace(fp);
65 *(Top::c_entry_fp_address()) = saved_c_frame_fp;
66}
67
68
69static void CheckRetAddrIsInFunction(const char* func_name,
70 Address ret_addr,
71 Address func_start_addr,
72 unsigned int func_len) {
73 printf("CheckRetAddrIsInFunction \"%s\": %p %p %p\n",
74 func_name, func_start_addr, ret_addr, func_start_addr + func_len);
75 CHECK_GE(ret_addr, func_start_addr);
76 CHECK_GE(func_start_addr + func_len, ret_addr);
77}
78
79
80static void CheckRetAddrIsInJSFunction(const char* func_name,
81 Address ret_addr,
82 Handle<JSFunction> func) {
83 v8::internal::Code* func_code = func->code();
84 CheckRetAddrIsInFunction(
85 func_name, ret_addr,
86 func_code->instruction_start(),
87 func_code->ExecutableSize());
88}
89
90
91// --- T r a c e E x t e n s i o n ---
92
93class TraceExtension : public v8::Extension {
94 public:
95 TraceExtension() : v8::Extension("v8/trace", kSource) { }
96 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
97 v8::Handle<String> name);
98 static v8::Handle<v8::Value> Trace(const v8::Arguments& args);
99 static v8::Handle<v8::Value> JSTrace(const v8::Arguments& args);
100 static v8::Handle<v8::Value> JSEntrySP(const v8::Arguments& args);
101 static v8::Handle<v8::Value> JSEntrySPLevel2(const v8::Arguments& args);
102 private:
103 static Address GetFP(const v8::Arguments& args);
104 static const char* kSource;
105};
106
107
108const char* TraceExtension::kSource =
109 "native function trace();"
110 "native function js_trace();"
111 "native function js_entry_sp();"
112 "native function js_entry_sp_level2();";
113
114v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction(
115 v8::Handle<String> name) {
116 if (name->Equals(String::New("trace"))) {
117 return v8::FunctionTemplate::New(TraceExtension::Trace);
118 } else if (name->Equals(String::New("js_trace"))) {
119 return v8::FunctionTemplate::New(TraceExtension::JSTrace);
120 } else if (name->Equals(String::New("js_entry_sp"))) {
121 return v8::FunctionTemplate::New(TraceExtension::JSEntrySP);
122 } else if (name->Equals(String::New("js_entry_sp_level2"))) {
123 return v8::FunctionTemplate::New(TraceExtension::JSEntrySPLevel2);
124 } else {
125 CHECK(false);
126 return v8::Handle<v8::FunctionTemplate>();
127 }
128}
129
130
131Address TraceExtension::GetFP(const v8::Arguments& args) {
132 CHECK_EQ(1, args.Length());
133 // CodeGenerator::GenerateGetFramePointer pushes EBP / RBP value
134 // on stack. In 64-bit mode we can't use Smi operations code because
135 // they check that value is within Smi bounds.
136 Address fp = *reinterpret_cast<Address*>(*args[0]);
137 printf("Trace: %p\n", fp);
138 return fp;
139}
140
141
142v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) {
143 DoTrace(GetFP(args));
144 return v8::Undefined();
145}
146
147
148v8::Handle<v8::Value> TraceExtension::JSTrace(const v8::Arguments& args) {
149 DoTraceHideCEntryFPAddress(GetFP(args));
150 return v8::Undefined();
151}
152
153
154static Address GetJsEntrySp() {
155 CHECK_NE(NULL, Top::GetCurrentThread());
156 return Top::js_entry_sp(Top::GetCurrentThread());
157}
158
159
160v8::Handle<v8::Value> TraceExtension::JSEntrySP(const v8::Arguments& args) {
161 CHECK_NE(0, GetJsEntrySp());
162 return v8::Undefined();
163}
164
165
Steve Blocka7e24c12009-10-30 11:49:00 +0000166v8::Handle<v8::Value> TraceExtension::JSEntrySPLevel2(
167 const v8::Arguments& args) {
168 v8::HandleScope scope;
169 const Address js_entry_sp = GetJsEntrySp();
170 CHECK_NE(0, js_entry_sp);
171 CompileRun("js_entry_sp();");
172 CHECK_EQ(js_entry_sp, GetJsEntrySp());
173 return v8::Undefined();
174}
175
176
177static TraceExtension kTraceExtension;
178v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension);
179
180
181static void InitializeVM() {
182 if (env.IsEmpty()) {
183 v8::HandleScope scope;
184 const char* extensions[] = { "v8/trace" };
185 v8::ExtensionConfiguration config(1, extensions);
186 env = v8::Context::New(&config);
187 }
188 v8::HandleScope scope;
189 env->Enter();
190}
191
192
193static Handle<JSFunction> CompileFunction(const char* source) {
Steve Block6ded16b2010-05-10 14:33:55 +0100194 Handle<JSFunction> result(JSFunction::cast(
195 *v8::Utils::OpenHandle(*Script::Compile(String::New(source)))));
196 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +0000197}
198
199
200static Local<Value> GetGlobalProperty(const char* name) {
201 return env->Global()->Get(String::New(name));
202}
203
204
205static Handle<JSFunction> GetGlobalJSFunction(const char* name) {
Steve Block6ded16b2010-05-10 14:33:55 +0100206 Handle<JSFunction> result(JSFunction::cast(
207 *v8::Utils::OpenHandle(*GetGlobalProperty(name))));
208 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +0000209}
210
211
212static void CheckRetAddrIsInJSFunction(const char* func_name,
Steve Block6ded16b2010-05-10 14:33:55 +0100213 Address ret_addr) {
214 CheckRetAddrIsInJSFunction(func_name,
215 ret_addr,
Steve Blocka7e24c12009-10-30 11:49:00 +0000216 GetGlobalJSFunction(func_name));
217}
218
219
220static void SetGlobalProperty(const char* name, Local<Value> value) {
221 env->Global()->Set(String::New(name), value);
222}
223
224
225static Handle<v8::internal::String> NewString(const char* s) {
226 return i::Factory::NewStringFromAscii(i::CStrVector(s));
227}
228
229
230namespace v8 {
231namespace internal {
232
233class CodeGeneratorPatcher {
234 public:
235 CodeGeneratorPatcher() {
236 CodeGenerator::InlineRuntimeLUT genGetFramePointer =
Steve Block6ded16b2010-05-10 14:33:55 +0100237 {&CodeGenerator::GenerateGetFramePointer, "_GetFramePointer", 0};
238 // _RandomHeapNumber is just used as a dummy function that has zero
239 // arguments, the same as the _GetFramePointer function we actually patch
240 // in.
Steve Blocka7e24c12009-10-30 11:49:00 +0000241 bool result = CodeGenerator::PatchInlineRuntimeEntry(
Steve Block6ded16b2010-05-10 14:33:55 +0100242 NewString("_RandomHeapNumber"),
Steve Blocka7e24c12009-10-30 11:49:00 +0000243 genGetFramePointer, &oldInlineEntry);
244 CHECK(result);
245 }
246
247 ~CodeGeneratorPatcher() {
248 CHECK(CodeGenerator::PatchInlineRuntimeEntry(
249 NewString("_GetFramePointer"),
250 oldInlineEntry, NULL));
251 }
252
253 private:
254 CodeGenerator::InlineRuntimeLUT oldInlineEntry;
255};
256
257} } // namespace v8::internal
258
259
260// Creates a global function named 'func_name' that calls the tracing
261// function 'trace_func_name' with an actual EBP register value,
262// shifted right to be presented as Smi.
263static void CreateTraceCallerFunction(const char* func_name,
264 const char* trace_func_name) {
265 i::EmbeddedVector<char, 256> trace_call_buf;
266 i::OS::SNPrintF(trace_call_buf, "%s(%%_GetFramePointer());", trace_func_name);
267
268 // Compile the script.
269 i::CodeGeneratorPatcher patcher;
270 bool allow_natives_syntax = i::FLAG_allow_natives_syntax;
271 i::FLAG_allow_natives_syntax = true;
272 Handle<JSFunction> func = CompileFunction(trace_call_buf.start());
273 CHECK(!func.is_null());
274 i::FLAG_allow_natives_syntax = allow_natives_syntax;
275
276#ifdef DEBUG
277 v8::internal::Code* func_code = func->code();
278 CHECK(func_code->IsCode());
279 func_code->Print();
280#endif
281
282 SetGlobalProperty(func_name, v8::ToApi<Value>(func));
Steve Block6ded16b2010-05-10 14:33:55 +0100283 CHECK_EQ(*func, *GetGlobalJSFunction(func_name));
Steve Blocka7e24c12009-10-30 11:49:00 +0000284}
285
286
Steve Block6ded16b2010-05-10 14:33:55 +0100287// This test verifies that stack tracing works when called during
288// execution of a native function called from JS code. In this case,
289// StackTracer uses Top::c_entry_fp as a starting point for stack
290// walking.
Steve Blocka7e24c12009-10-30 11:49:00 +0000291TEST(CFromJSStackTrace) {
292 TickSample sample;
293 InitTraceEnv(&sample);
294
295 InitializeVM();
296 v8::HandleScope scope;
Steve Block6ded16b2010-05-10 14:33:55 +0100297 // Create global function JSFuncDoTrace which calls
298 // extension function trace() with the current frame pointer value.
Steve Blocka7e24c12009-10-30 11:49:00 +0000299 CreateTraceCallerFunction("JSFuncDoTrace", "trace");
Steve Block6ded16b2010-05-10 14:33:55 +0100300 Local<Value> result = CompileRun(
Steve Blocka7e24c12009-10-30 11:49:00 +0000301 "function JSTrace() {"
302 " JSFuncDoTrace();"
303 "};\n"
Steve Block6ded16b2010-05-10 14:33:55 +0100304 "JSTrace();\n"
305 "true;");
306 CHECK(!result.IsEmpty());
307 // When stack tracer is invoked, the stack should look as follows:
308 // script [JS]
309 // JSTrace() [JS]
310 // JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
311 // trace(EBP encoded as Smi) [native (extension)]
312 // DoTrace(EBP) [native]
313 // StackTracer::Trace
Steve Blocka7e24c12009-10-30 11:49:00 +0000314 CHECK_GT(sample.frames_count, 1);
Steve Block6ded16b2010-05-10 14:33:55 +0100315 // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
Steve Blocka7e24c12009-10-30 11:49:00 +0000316 CheckRetAddrIsInJSFunction("JSFuncDoTrace",
317 sample.stack[0]);
318 CheckRetAddrIsInJSFunction("JSTrace",
319 sample.stack[1]);
320}
321
322
Steve Block6ded16b2010-05-10 14:33:55 +0100323// This test verifies that stack tracing works when called during
324// execution of JS code. However, as calling StackTracer requires
325// entering native code, we can only emulate pure JS by erasing
326// Top::c_entry_fp value. In this case, StackTracer uses passed frame
327// pointer value as a starting point for stack walking.
Steve Blocka7e24c12009-10-30 11:49:00 +0000328TEST(PureJSStackTrace) {
329 TickSample sample;
330 InitTraceEnv(&sample);
331
332 InitializeVM();
333 v8::HandleScope scope;
Steve Block6ded16b2010-05-10 14:33:55 +0100334 // Create global function JSFuncDoTrace which calls
335 // extension function js_trace() with the current frame pointer value.
Steve Blocka7e24c12009-10-30 11:49:00 +0000336 CreateTraceCallerFunction("JSFuncDoTrace", "js_trace");
Steve Block6ded16b2010-05-10 14:33:55 +0100337 Local<Value> result = CompileRun(
Steve Blocka7e24c12009-10-30 11:49:00 +0000338 "function JSTrace() {"
339 " JSFuncDoTrace();"
340 "};\n"
341 "function OuterJSTrace() {"
342 " JSTrace();"
343 "};\n"
Steve Block6ded16b2010-05-10 14:33:55 +0100344 "OuterJSTrace();\n"
345 "true;");
346 CHECK(!result.IsEmpty());
347 // When stack tracer is invoked, the stack should look as follows:
348 // script [JS]
349 // OuterJSTrace() [JS]
350 // JSTrace() [JS]
351 // JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
352 // js_trace(EBP encoded as Smi) [native (extension)]
353 // DoTraceHideCEntryFPAddress(EBP) [native]
354 // StackTracer::Trace
355 //
356 // The last JS function called. It is only visible through
357 // sample.function, as its return address is above captured EBP value.
Leon Clarked91b9f72010-01-27 17:25:45 +0000358 CHECK_EQ(GetGlobalJSFunction("JSFuncDoTrace")->address(),
359 sample.function);
Steve Blocka7e24c12009-10-30 11:49:00 +0000360 CHECK_GT(sample.frames_count, 1);
361 // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
362 CheckRetAddrIsInJSFunction("JSTrace",
363 sample.stack[0]);
364 CheckRetAddrIsInJSFunction("OuterJSTrace",
365 sample.stack[1]);
366}
367
368
Steve Blockd0582a62009-12-15 09:54:21 +0000369static void CFuncDoTrace(byte dummy_parameter) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000370 Address fp;
371#ifdef __GNUC__
372 fp = reinterpret_cast<Address>(__builtin_frame_address(0));
Steve Blockd0582a62009-12-15 09:54:21 +0000373#elif defined _MSC_VER
374 // Approximate a frame pointer address. We compile without base pointers,
375 // so we can't trust ebp/rbp.
376 fp = &dummy_parameter - 2 * sizeof(void*); // NOLINT
377#else
378#error Unexpected platform.
Steve Blocka7e24c12009-10-30 11:49:00 +0000379#endif
380 DoTrace(fp);
381}
382
383
384static int CFunc(int depth) {
385 if (depth <= 0) {
Steve Blockd0582a62009-12-15 09:54:21 +0000386 CFuncDoTrace(0);
Steve Blocka7e24c12009-10-30 11:49:00 +0000387 return 0;
388 } else {
389 return CFunc(depth - 1) + 1;
390 }
391}
392
393
Steve Block6ded16b2010-05-10 14:33:55 +0100394// This test verifies that stack tracing doesn't crash when called on
395// pure native code. StackTracer only unrolls JS code, so we can't
396// get any meaningful info here.
Steve Blocka7e24c12009-10-30 11:49:00 +0000397TEST(PureCStackTrace) {
398 TickSample sample;
399 InitTraceEnv(&sample);
400 // Check that sampler doesn't crash
401 CHECK_EQ(10, CFunc(10));
402}
403
404
405TEST(JsEntrySp) {
406 InitializeVM();
407 v8::HandleScope scope;
408 CHECK_EQ(0, GetJsEntrySp());
409 CompileRun("a = 1; b = a + 1;");
410 CHECK_EQ(0, GetJsEntrySp());
411 CompileRun("js_entry_sp();");
412 CHECK_EQ(0, GetJsEntrySp());
413 CompileRun("js_entry_sp_level2();");
414 CHECK_EQ(0, GetJsEntrySp());
415}
416
417#endif // ENABLE_LOGGING_AND_PROFILING