From 1642f32d1c568dea0ebe4f6e7d8e68e994815276 Mon Sep 17 00:00:00 2001 From: "danno@chromium.org" Date: Fri, 28 Jun 2013 13:40:41 +0000 Subject: [PATCH] Improved function entry hook coverage MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Adds more coverage for function entry hook, sufficient to capture profiles that are contiguous from C++, through JS and back out to C++. R=danno@chromium.org Committed: http://code.google.com/p/v8/source/detail?r=15361 Review URL: https://codereview.chromium.org/16578008 Patch from Sigurður Ásgeirsson . git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15384 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 13 +- src/api.cc | 38 ++++- src/arm/builtins-arm.cc | 1 + src/arm/code-stubs-arm.cc | 38 +++-- src/bootstrapper.cc | 9 +- src/code-stubs.cc | 19 +-- src/code-stubs.h | 10 -- src/frames-inl.h | 11 ++ src/frames.cc | 21 +-- src/frames.h | 6 + src/ia32/builtins-ia32.cc | 2 + src/ia32/code-stubs-ia32.cc | 24 ++- src/ic.cc | 2 +- src/isolate.cc | 13 ++ src/isolate.h | 13 +- src/v8.h | 2 + src/x64/builtins-x64.cc | 2 + src/x64/code-stubs-x64.cc | 66 +++----- test/cctest/test-api.cc | 374 ++++++++++++++++++++++++++++++++++---------- 19 files changed, 473 insertions(+), 191 deletions(-) mode change 100644 => 100755 src/arm/code-stubs-arm.cc mode change 100755 => 100644 test/cctest/test-api.cc diff --git a/include/v8.h b/include/v8.h index 2ccab5e..cf01684 100644 --- a/include/v8.h +++ b/include/v8.h @@ -4477,18 +4477,25 @@ class V8EXPORT V8 { ReturnAddressLocationResolver return_address_resolver); /** + * Deprecated, use the variant with the Isolate parameter below instead. + */ + V8_DEPRECATED(static bool SetFunctionEntryHook(FunctionEntryHook entry_hook)); + + /** * Allows the host application to provide the address of a function that's * invoked on entry to every V8-generated function. * Note that \p entry_hook is invoked at the very start of each * generated function. * + * \param isolate the isolate to operate on. * \param entry_hook a function that will be invoked on entry to every * V8-generated function. * \returns true on success on supported platforms, false on failure. - * \note Setting a new entry hook function when one is already active will - * fail. + * \note Setting an entry hook can only be done very early in an isolates + * lifetime, and once set, the entry hook cannot be revoked. */ - static bool SetFunctionEntryHook(FunctionEntryHook entry_hook); + static bool SetFunctionEntryHook(Isolate* isolate, + FunctionEntryHook entry_hook); /** * Allows the host application to provide the address of a function that is diff --git a/src/api.cc b/src/api.cc index 014d82e..edffca1 100644 --- a/src/api.cc +++ b/src/api.cc @@ -300,8 +300,13 @@ static inline bool EmptyCheck(const char* location, const v8::Data* obj) { // --- S t a t i c s --- -static bool InitializeHelper() { - if (i::Snapshot::Initialize()) return true; +static bool InitializeHelper(i::Isolate* isolate) { + // If the isolate has a function entry hook, it needs to re-build all its + // code stubs with entry hooks embedded, so let's deserialize a snapshot. + if (isolate == NULL || isolate->function_entry_hook() == NULL) { + if (i::Snapshot::Initialize()) + return true; + } return i::V8::Initialize(NULL); } @@ -313,7 +318,7 @@ static inline bool EnsureInitializedForIsolate(i::Isolate* isolate, if (isolate->IsInitialized()) return true; } ASSERT(isolate == i::Isolate::Current()); - return ApiCheck(InitializeHelper(), location, "Error initializing V8"); + return ApiCheck(InitializeHelper(isolate), location, "Error initializing V8"); } // Some initializing API functions are called early and may be @@ -5211,7 +5216,7 @@ bool v8::V8::Initialize() { if (isolate != NULL && isolate->IsInitialized()) { return true; } - return InitializeHelper(); + return InitializeHelper(isolate); } @@ -5227,7 +5232,30 @@ void v8::V8::SetReturnAddressLocationResolver( bool v8::V8::SetFunctionEntryHook(FunctionEntryHook entry_hook) { - return i::ProfileEntryHookStub::SetFunctionEntryHook(entry_hook); + return SetFunctionEntryHook(Isolate::GetCurrent(), entry_hook); +} + + +bool v8::V8::SetFunctionEntryHook(Isolate* ext_isolate, + FunctionEntryHook entry_hook) { + ASSERT(ext_isolate != NULL); + ASSERT(entry_hook != NULL); + + i::Isolate* isolate = reinterpret_cast(ext_isolate); + + // The entry hook can only be set before the Isolate is initialized, as + // otherwise the Isolate's code stubs generated at initialization won't + // contain entry hooks. + if (isolate->IsInitialized()) + return false; + + // Setting an entry hook is a one-way operation, once set, it cannot be + // changed or unset. + if (isolate->function_entry_hook() != NULL) + return false; + + isolate->set_function_entry_hook(entry_hook); + return true; } diff --git a/src/arm/builtins-arm.cc b/src/arm/builtins-arm.cc index 2641975..6b3caf3 100644 --- a/src/arm/builtins-arm.cc +++ b/src/arm/builtins-arm.cc @@ -717,6 +717,7 @@ static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, // r3: argc // r4: argv // r5-r7, cp may be clobbered + ProfileEntryHookStub::MaybeCallEntryHook(masm); // Clear the context before we push it when entering the internal frame. __ mov(cp, Operand::Zero()); diff --git a/src/arm/code-stubs-arm.cc b/src/arm/code-stubs-arm.cc old mode 100644 new mode 100755 index 7659f76..8e9237c --- a/src/arm/code-stubs-arm.cc +++ b/src/arm/code-stubs-arm.cc @@ -3181,6 +3181,8 @@ void CEntryStub::Generate(MacroAssembler* masm) { // sp: stack pointer (restored as callee's sp after C call) // cp: current context (C callee-saved) + ProfileEntryHookStub::MaybeCallEntryHook(masm); + // Result returned in r0 or r0+r1 by default. // NOTE: Invocations of builtins may return failure objects @@ -3271,6 +3273,8 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { Label invoke, handler_entry, exit; + ProfileEntryHookStub::MaybeCallEntryHook(masm); + // Called from C, so do not pop argc and args on exit (preserve sp) // No need to save register-passed args // Save callee-saved registers (incl. cp and fp), sp, and lr @@ -7074,8 +7078,9 @@ void StubFailureTrampolineStub::Generate(MacroAssembler* masm) { void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) { - if (entry_hook_ != NULL) { + if (masm->isolate()->function_entry_hook() != NULL) { PredictableCodeSizeScope predictable(masm, 4 * Assembler::kInstrSize); + AllowStubCallsScope allow_stub_calls(masm, true); ProfileEntryHookStub stub; __ push(lr); __ CallStub(&stub); @@ -7089,9 +7094,21 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) { const int32_t kReturnAddressDistanceFromFunctionStart = 3 * Assembler::kInstrSize; - // Save live volatile registers. - __ Push(lr, r5, r1); - const int32_t kNumSavedRegs = 3; + // This should contain all kCallerSaved registers. + const RegList kSavedRegs = + 1 << 0 | // r0 + 1 << 1 | // r1 + 1 << 2 | // r2 + 1 << 3 | // r3 + 1 << 5 | // r5 + 1 << 9; // r9 + // We also save lr, so the count here is one higher than the mask indicates. + const int32_t kNumSavedRegs = 7; + + ASSERT((kCallerSaved & kSavedRegs) == kCallerSaved); + + // Save all caller-save registers as this may be called from anywhere. + __ stm(db_w, sp, kSavedRegs | lr.bit()); // Compute the function's address for the first argument. __ sub(r0, lr, Operand(kReturnAddressDistanceFromFunctionStart)); @@ -7109,14 +7126,13 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) { } #if defined(V8_HOST_ARCH_ARM) - __ mov(ip, Operand(reinterpret_cast(&entry_hook_))); - __ ldr(ip, MemOperand(ip)); + int32_t entry_hook = + reinterpret_cast(masm->isolate()->function_entry_hook()); + __ mov(ip, Operand(entry_hook)); #else // Under the simulator we need to indirect the entry hook through a // trampoline function at a known address. - Address trampoline_address = reinterpret_cast
( - reinterpret_cast(EntryHookTrampoline)); - ApiFunction dispatcher(trampoline_address); + ApiFunction dispatcher(FUNCTION_ADDR(EntryHookTrampoline)); __ mov(ip, Operand(ExternalReference(&dispatcher, ExternalReference::BUILTIN_CALL, masm->isolate()))); @@ -7128,8 +7144,8 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) { __ mov(sp, r5); } - __ Pop(lr, r5, r1); - __ Ret(); + // Also pop pc to get Ret(0). + __ ldm(ia_w, sp, kSavedRegs | pc.bit()); } diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index c3d1c3a..d5b46de 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -2580,7 +2580,14 @@ Genesis::Genesis(Isolate* isolate, StackLimitCheck check(isolate); if (check.HasOverflowed()) return; - native_context_ = Snapshot::NewContextFromSnapshot(); + // We can only de-serialize a context if the isolate was initialized from + // a snapshot. Otherwise we have to build the context from scratch. + if (isolate->initialized_from_snapshot()) { + native_context_ = Snapshot::NewContextFromSnapshot(); + } else { + native_context_ = Handle(); + } + if (!native_context().is_null()) { AddToWeakNativeContextList(*native_context()); isolate->set_context(*native_context()); diff --git a/src/code-stubs.cc b/src/code-stubs.cc index 5bec7e0..2d94ec9 100644 --- a/src/code-stubs.cc +++ b/src/code-stubs.cc @@ -757,24 +757,11 @@ void StubFailureTrampolineStub::GenerateAheadOfTime(Isolate* isolate) { } -FunctionEntryHook ProfileEntryHookStub::entry_hook_ = NULL; - - void ProfileEntryHookStub::EntryHookTrampoline(intptr_t function, intptr_t stack_pointer) { - if (entry_hook_ != NULL) - entry_hook_(function, stack_pointer); -} - - -bool ProfileEntryHookStub::SetFunctionEntryHook(FunctionEntryHook entry_hook) { - // We don't allow setting a new entry hook over one that's - // already active, as the hooks won't stack. - if (entry_hook != 0 && entry_hook_ != 0) - return false; - - entry_hook_ = entry_hook; - return true; + FunctionEntryHook entry_hook = Isolate::Current()->function_entry_hook(); + ASSERT(entry_hook != NULL); + entry_hook(function, stack_pointer); } diff --git a/src/code-stubs.h b/src/code-stubs.h index 1340590..c7076b6 100644 --- a/src/code-stubs.h +++ b/src/code-stubs.h @@ -2168,13 +2168,6 @@ class ProfileEntryHookStub : public PlatformCodeStub { // Generates a call to the entry hook if it's enabled. static void MaybeCallEntryHook(MacroAssembler* masm); - // Sets or unsets the entry hook function. Returns true on success, - // false on an attempt to replace a non-NULL entry hook with another - // non-NULL hook. - static bool SetFunctionEntryHook(FunctionEntryHook entry_hook); - - static bool HasEntryHook() { return entry_hook_ != NULL; } - private: static void EntryHookTrampoline(intptr_t function, intptr_t stack_pointer); @@ -2184,9 +2177,6 @@ class ProfileEntryHookStub : public PlatformCodeStub { void Generate(MacroAssembler* masm); - // The current function entry hook. - static FunctionEntryHook entry_hook_; - DISALLOW_COPY_AND_ASSIGN(ProfileEntryHookStub); }; diff --git a/src/frames-inl.h b/src/frames-inl.h index bd652da..8d10645 100644 --- a/src/frames-inl.h +++ b/src/frames-inl.h @@ -136,6 +136,17 @@ inline Code* StackFrame::GetContainingCode(Isolate* isolate, Address pc) { } +inline Address* StackFrame::ResolveReturnAddressLocation(Address* pc_address) { + if (return_address_location_resolver_ == NULL) { + return pc_address; + } else { + return reinterpret_cast( + return_address_location_resolver_( + reinterpret_cast(pc_address))); + } +} + + inline EntryFrame::EntryFrame(StackFrameIteratorBase* iterator) : StackFrame(iterator) { } diff --git a/src/frames.cc b/src/frames.cc index 0ca6991..edd5ddd 100644 --- a/src/frames.cc +++ b/src/frames.cc @@ -43,19 +43,8 @@ namespace v8 { namespace internal { -static ReturnAddressLocationResolver return_address_location_resolver = NULL; - - -// Resolves pc_address through the resolution address function if one is set. -static inline Address* ResolveReturnAddressLocation(Address* pc_address) { - if (return_address_location_resolver == NULL) { - return pc_address; - } else { - return reinterpret_cast( - return_address_location_resolver( - reinterpret_cast(pc_address))); - } -} +ReturnAddressLocationResolver + StackFrame::return_address_location_resolver_ = NULL; // Iterator that supports traversing the stack handlers of a @@ -239,7 +228,7 @@ SafeStackFrameIterator::SafeStackFrameIterator( ASSERT(fp != NULL); state.fp = fp; state.sp = sp; - state.pc_address = ResolveReturnAddressLocation( + state.pc_address = StackFrame::ResolveReturnAddressLocation( reinterpret_cast(StandardFrame::ComputePCAddress(fp))); type = StackFrame::ComputeType(this, &state); } else { @@ -389,8 +378,8 @@ void StackFrame::IteratePc(ObjectVisitor* v, void StackFrame::SetReturnAddressLocationResolver( ReturnAddressLocationResolver resolver) { - ASSERT(return_address_location_resolver == NULL); - return_address_location_resolver = resolver; + ASSERT(return_address_location_resolver_ == NULL); + return_address_location_resolver_ = resolver; } diff --git a/src/frames.h b/src/frames.h index 19e4d60..9ca218a 100644 --- a/src/frames.h +++ b/src/frames.h @@ -297,6 +297,10 @@ class StackFrame BASE_EMBEDDED { static void SetReturnAddressLocationResolver( ReturnAddressLocationResolver resolver); + // Resolves pc_address through the resolution address function if one is set. + static inline Address* ResolveReturnAddressLocation(Address* pc_address); + + // Printing support. enum PrintMode { OVERVIEW, DETAILS }; virtual void Print(StringStream* accumulator, @@ -332,6 +336,8 @@ class StackFrame BASE_EMBEDDED { Isolate* isolate_; State state_; + static ReturnAddressLocationResolver return_address_location_resolver_; + // Fill in the state of the calling frame. virtual void ComputeCallerState(State* state) const = 0; diff --git a/src/ia32/builtins-ia32.cc b/src/ia32/builtins-ia32.cc index c3490a3..93400ae 100644 --- a/src/ia32/builtins-ia32.cc +++ b/src/ia32/builtins-ia32.cc @@ -447,6 +447,8 @@ void Builtins::Generate_JSConstructStubApi(MacroAssembler* masm) { static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, bool is_construct) { + ProfileEntryHookStub::MaybeCallEntryHook(masm); + // Clear the context before we push it when entering the internal frame. __ Set(esi, Immediate(0)); diff --git a/src/ia32/code-stubs-ia32.cc b/src/ia32/code-stubs-ia32.cc index 7bb0f5d..8cd4685 100644 --- a/src/ia32/code-stubs-ia32.cc +++ b/src/ia32/code-stubs-ia32.cc @@ -5080,6 +5080,8 @@ void CEntryStub::Generate(MacroAssembler* masm) { // esi: current context (C callee-saved) // edi: JS function of the caller (C callee-saved) + ProfileEntryHookStub::MaybeCallEntryHook(masm); + // NOTE: Invocations of builtins may return failure objects instead // of a proper result. The builtin entry handles this by performing // a garbage collection and retrying the builtin (twice). @@ -5153,6 +5155,8 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { Label invoke, handler_entry, exit; Label not_outermost_js, not_outermost_js_2; + ProfileEntryHookStub::MaybeCallEntryHook(masm); + // Set up frame. __ push(ebp); __ mov(ebp, esp); @@ -7694,7 +7698,11 @@ void StubFailureTrampolineStub::Generate(MacroAssembler* masm) { void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) { - if (entry_hook_ != NULL) { + if (masm->isolate()->function_entry_hook() != NULL) { + // It's always safe to call the entry hook stub, as the hook itself + // is not allowed to call back to V8. + AllowStubCallsScope allow_stub_calls(masm, true); + ProfileEntryHookStub stub; masm->CallStub(&stub); } @@ -7702,9 +7710,11 @@ void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) { void ProfileEntryHookStub::Generate(MacroAssembler* masm) { - // Ecx is the only volatile register we must save. - const int kNumSavedRegisters = 1; + // Save volatile registers. + const int kNumSavedRegisters = 3; + __ push(eax); __ push(ecx); + __ push(edx); // Calculate and push the original stack pointer. __ lea(eax, Operand(esp, (kNumSavedRegisters + 1) * kPointerSize)); @@ -7717,12 +7727,16 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) { __ push(eax); // Call the entry hook. - int32_t hook_location = reinterpret_cast(&entry_hook_); - __ call(Operand(hook_location, RelocInfo::NONE32)); + ASSERT(masm->isolate()->function_entry_hook() != NULL); + __ call(FUNCTION_ADDR(masm->isolate()->function_entry_hook()), + RelocInfo::RUNTIME_ENTRY); __ add(esp, Immediate(2 * kPointerSize)); // Restore ecx. + __ pop(edx); __ pop(ecx); + __ pop(eax); + __ ret(0); } diff --git a/src/ic.cc b/src/ic.cc index 3995561..ff3a94d 100644 --- a/src/ic.cc +++ b/src/ic.cc @@ -144,7 +144,7 @@ IC::IC(FrameDepth depth, Isolate* isolate) : isolate_(isolate) { ASSERT(fp == frame->fp() && pc_address == frame->pc_address()); #endif fp_ = fp; - pc_address_ = pc_address; + pc_address_ = StackFrame::ResolveReturnAddressLocation(pc_address); } diff --git a/src/isolate.cc b/src/isolate.cc index 476e405..2383399 100644 --- a/src/isolate.cc +++ b/src/isolate.cc @@ -1753,8 +1753,10 @@ Isolate::Isolate() date_cache_(NULL), code_stub_interface_descriptors_(NULL), context_exit_happened_(false), + initialized_from_snapshot_(false), cpu_profiler_(NULL), heap_profiler_(NULL), + function_entry_hook_(NULL), deferred_handles_head_(NULL), optimizing_compiler_thread_(this), marking_thread_(NULL), @@ -2077,6 +2079,14 @@ bool Isolate::Init(Deserializer* des) { ASSERT(Isolate::Current() == this); TRACE_ISOLATE(init); + if (function_entry_hook() != NULL) { + // When function entry hooking is in effect, we have to create the code + // stubs from scratch to get entry hooks, rather than loading the previously + // generated stubs from disk. + // If this assert fires, the initialization path has regressed. + ASSERT(des == NULL); + } + // The initialization process does not handle memory exhaustion. DisallowAllocationFailure disallow_allocation_failure; @@ -2268,6 +2278,9 @@ bool Isolate::Init(Deserializer* des) { sweeper_thread_[i]->Start(); } } + + initialized_from_snapshot_ = (des != NULL); + return true; } diff --git a/src/isolate.h b/src/isolate.h index e6f4169..981f81c 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -547,7 +547,7 @@ class Isolate { } Context** context_address() { return &thread_local_top_.context_; } - SaveContext* save_context() {return thread_local_top_.save_context_; } + SaveContext* save_context() { return thread_local_top_.save_context_; } void set_save_context(SaveContext* save) { thread_local_top_.save_context_ = save; } @@ -1050,6 +1050,8 @@ class Isolate { context_exit_happened_ = context_exit_happened; } + bool initialized_from_snapshot() { return initialized_from_snapshot_; } + double time_millis_since_init() { return OS::TimeCurrentMillis() - time_millis_at_init_; } @@ -1109,6 +1111,11 @@ class Isolate { HStatistics* GetHStatistics(); HTracer* GetHTracer(); + FunctionEntryHook function_entry_hook() { return function_entry_hook_; } + void set_function_entry_hook(FunctionEntryHook function_entry_hook) { + function_entry_hook_ = function_entry_hook; + } + private: Isolate(); @@ -1288,6 +1295,9 @@ class Isolate { // that a context was recently exited. bool context_exit_happened_; + // True if this isolate was initialized from a snapshot. + bool initialized_from_snapshot_; + // Time stamp at initialization. double time_millis_at_init_; @@ -1311,6 +1321,7 @@ class Isolate { #endif CpuProfiler* cpu_profiler_; HeapProfiler* heap_profiler_; + FunctionEntryHook function_entry_hook_; #define GLOBAL_BACKING_STORE(type, name, initialvalue) \ type name##_; diff --git a/src/v8.h b/src/v8.h index b16374f..52fb98a 100644 --- a/src/v8.h +++ b/src/v8.h @@ -101,6 +101,8 @@ class V8 : public AllStatic { // Support for return-address rewriting profilers. static void SetReturnAddressLocationResolver( ReturnAddressLocationResolver resolver); + // Support for entry hooking JITed code. + static void SetFunctionEntryHook(FunctionEntryHook entry_hook); // Random number generation support. Not cryptographically safe. static uint32_t Random(Context* context); // We use random numbers internally in memory allocation and in the diff --git a/src/x64/builtins-x64.cc b/src/x64/builtins-x64.cc index 89ae74d..9376cc7 100644 --- a/src/x64/builtins-x64.cc +++ b/src/x64/builtins-x64.cc @@ -456,6 +456,8 @@ void Builtins::Generate_JSConstructStubApi(MacroAssembler* masm) { static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, bool is_construct) { + ProfileEntryHookStub::MaybeCallEntryHook(masm); + // Expects five C++ function parameters. // - Address entry (ignored) // - JSFunction* function ( diff --git a/src/x64/code-stubs-x64.cc b/src/x64/code-stubs-x64.cc index d8982d6..1be60ab 100644 --- a/src/x64/code-stubs-x64.cc +++ b/src/x64/code-stubs-x64.cc @@ -4132,6 +4132,8 @@ void CEntryStub::Generate(MacroAssembler* masm) { // this by performing a garbage collection and retrying the // builtin once. + ProfileEntryHookStub::MaybeCallEntryHook(masm); + // Enter the exit frame that transitions from JavaScript to C++. #ifdef _WIN64 int arg_stack_space = (result_size_ < 2 ? 2 : 4); @@ -4212,6 +4214,8 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { Label invoke, handler_entry, exit; Label not_outermost_js, not_outermost_js_2; + ProfileEntryHookStub::MaybeCallEntryHook(masm); + { // NOLINT. Scope block confuses linter. MacroAssembler::NoRootArrayScope uninitialized_root_register(masm); // Set up frame. @@ -6666,7 +6670,11 @@ void StubFailureTrampolineStub::Generate(MacroAssembler* masm) { void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) { - if (entry_hook_ != NULL) { + if (masm->isolate()->function_entry_hook() != NULL) { + // It's always safe to call the entry hook stub, as the hook itself + // is not allowed to call back to V8. + AllowStubCallsScope allow_stub_calls(masm, true); + ProfileEntryHookStub stub; masm->CallStub(&stub); } @@ -6674,45 +6682,25 @@ void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) { void ProfileEntryHookStub::Generate(MacroAssembler* masm) { - // Save volatile registers. - // Live registers at this point are the same as at the start of any - // JS function: - // o rdi: the JS function object being called (i.e. ourselves) - // o rsi: our context - // o rbp: our caller's frame pointer - // o rsp: stack pointer (pointing to return address) - // o rcx: rcx is zero for method calls and non-zero for function calls. -#ifdef _WIN64 - const int kNumSavedRegisters = 1; - - __ push(rcx); -#else - const int kNumSavedRegisters = 3; - - __ push(rcx); - __ push(rdi); - __ push(rsi); -#endif + // This stub can be called from essentially anywhere, so it needs to save + // all volatile and callee-save registers. + const size_t kNumSavedRegisters = 2; + __ push(arg_reg_1); + __ push(arg_reg_2); // Calculate the original stack pointer and store it in the second arg. -#ifdef _WIN64 - __ lea(rdx, Operand(rsp, (kNumSavedRegisters + 1) * kPointerSize)); -#else - __ lea(rsi, Operand(rsp, (kNumSavedRegisters + 1) * kPointerSize)); -#endif + __ lea(arg_reg_2, Operand(rsp, (kNumSavedRegisters + 1) * kPointerSize)); // Calculate the function address to the first arg. -#ifdef _WIN64 - __ movq(rcx, Operand(rsp, kNumSavedRegisters * kPointerSize)); - __ subq(rcx, Immediate(Assembler::kShortCallInstructionLength)); -#else - __ movq(rdi, Operand(rsp, kNumSavedRegisters * kPointerSize)); - __ subq(rdi, Immediate(Assembler::kShortCallInstructionLength)); -#endif + __ movq(arg_reg_1, Operand(rsp, kNumSavedRegisters * kPointerSize)); + __ subq(arg_reg_1, Immediate(Assembler::kShortCallInstructionLength)); + + // Save the remainder of the volatile registers. + masm->PushCallerSaved(kSaveFPRegs, arg_reg_1, arg_reg_2); // Call the entry hook function. - __ movq(rax, &entry_hook_, RelocInfo::NONE64); - __ movq(rax, Operand(rax, 0)); + __ movq(rax, FUNCTION_ADDR(masm->isolate()->function_entry_hook()), + RelocInfo::NONE64); AllowExternalCallThatCantCauseGC scope(masm); @@ -6721,13 +6709,9 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) { __ CallCFunction(rax, kArgumentCount); // Restore volatile regs. -#ifdef _WIN64 - __ pop(rcx); -#else - __ pop(rsi); - __ pop(rdi); - __ pop(rcx); -#endif + masm->PopCallerSaved(kSaveFPRegs, arg_reg_1, arg_reg_2); + __ pop(arg_reg_2); + __ pop(arg_reg_1); __ Ret(); } diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc old mode 100755 new mode 100644 index b95f292..90a9389 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -31,6 +31,8 @@ #include // kill #include // getpid #endif // WIN32 +#include +#include #include "v8.h" @@ -12263,68 +12265,268 @@ THREADED_TEST(NestedHandleScopeAndContexts) { } -static i::Handle* foo_ptr = NULL; -static int foo_entry_count = 0; -static i::Handle* bar_ptr = NULL; -static int bar_entry_count = 0; -static int bar_caller_count = 0; +static bool MatchPointers(void* key1, void* key2) { + return key1 == key2; +} + + +struct SymbolInfo { + size_t id; + size_t size; + std::string name; +}; + + +class SetFunctionEntryHookTest { + public: + SetFunctionEntryHookTest() { + CHECK(instance_ == NULL); + instance_ = this; + } + ~SetFunctionEntryHookTest() { + CHECK(instance_ == this); + instance_ = NULL; + } + void Reset() { + symbols_.clear(); + symbol_locations_.clear(); + invocations_.clear(); + } + void RunTest(); + void OnJitEvent(const v8::JitCodeEvent* event); + static void JitEvent(const v8::JitCodeEvent* event) { + CHECK(instance_ != NULL); + instance_->OnJitEvent(event); + } + + void OnEntryHook(uintptr_t function, + uintptr_t return_addr_location); + static void EntryHook(uintptr_t function, + uintptr_t return_addr_location) { + CHECK(instance_ != NULL); + instance_->OnEntryHook(function, return_addr_location); + } + + static void RuntimeCallback(const v8::FunctionCallbackInfo& args) { + CHECK(instance_ != NULL); + args.GetReturnValue().Set(v8_num(42)); + } + void RunLoopInNewEnv(v8::Isolate* isolate); + + // Records addr as location of symbol. + void InsertSymbolAt(i::Address addr, SymbolInfo* symbol); + + // Finds the symbol containing addr + SymbolInfo* FindSymbolForAddr(i::Address addr); + // Returns the number of invocations where the caller name contains + // \p caller_name and the function name contains \p function_name. + int CountInvocations(const char* caller_name, + const char* function_name); + + i::Handle foo_func_; + i::Handle bar_func_; + + typedef std::map SymbolMap; + typedef std::map SymbolLocationMap; + typedef std::map, int> InvocationMap; + SymbolMap symbols_; + SymbolLocationMap symbol_locations_; + InvocationMap invocations_; + + static SetFunctionEntryHookTest* instance_; +}; +SetFunctionEntryHookTest* SetFunctionEntryHookTest::instance_ = NULL; + + +// Returns true if addr is in the range [start, start+len). +static bool Overlaps(i::Address start, size_t len, i::Address addr) { + if (start <= addr && start + len > addr) + return true; + + return false; +} + +void SetFunctionEntryHookTest::InsertSymbolAt(i::Address addr, + SymbolInfo* symbol) { + // Insert the symbol at the new location. + SymbolLocationMap::iterator it = + symbol_locations_.insert(std::make_pair(addr, symbol)).first; + // Now erase symbols to the left and right that overlap this one. + while (it != symbol_locations_.begin()) { + SymbolLocationMap::iterator left = it; + --left; + if (!Overlaps(left->first, left->second->size, addr)) + break; + symbol_locations_.erase(left); + } + + // Now erase symbols to the left and right that overlap this one. + while (true) { + SymbolLocationMap::iterator right = it; + ++right; + if (right == symbol_locations_.end()) + break; + if (!Overlaps(addr, symbol->size, right->first)) + break; + symbol_locations_.erase(right); + } +} + +void SetFunctionEntryHookTest::OnJitEvent(const v8::JitCodeEvent* event) { + switch (event->type) { + case v8::JitCodeEvent::CODE_ADDED: { + CHECK(event->code_start != NULL); + CHECK_NE(0, static_cast(event->code_len)); + CHECK(event->name.str != NULL); + size_t symbol_id = symbols_.size(); + + // Record the new symbol. + SymbolInfo& info = symbols_[symbol_id]; + info.id = symbol_id; + info.size = event->code_len; + info.name.assign(event->name.str, event->name.str + event->name.len); + + // And record it's location. + InsertSymbolAt(reinterpret_cast(event->code_start), &info); + } + break; + + case v8::JitCodeEvent::CODE_MOVED: { + // We would like to never see code move that we haven't seen before, + // but the code creation event does not happen until the line endings + // have been calculated (this is so that we can report the line in the + // script at which the function source is found, see + // Compiler::RecordFunctionCompilation) and the line endings + // calculations can cause a GC, which can move the newly created code + // before its existence can be logged. + SymbolLocationMap::iterator it( + symbol_locations_.find( + reinterpret_cast(event->code_start))); + if (it != symbol_locations_.end()) { + // Found a symbol at this location, move it. + SymbolInfo* info = it->second; + symbol_locations_.erase(it); + InsertSymbolAt(reinterpret_cast(event->new_code_start), + info); + } + } + default: + break; + } +} -static void entry_hook(uintptr_t function, - uintptr_t return_addr_location) { - i::Code* code = i::Code::GetCodeFromTargetAddress( +void SetFunctionEntryHookTest::OnEntryHook( + uintptr_t function, uintptr_t return_addr_location) { + // Get the function's code object. + i::Code* function_code = i::Code::GetCodeFromTargetAddress( reinterpret_cast(function)); - CHECK(code != NULL); + CHECK(function_code != NULL); - if (bar_ptr != NULL && code == (*bar_ptr)->code()) - ++bar_entry_count; + // Then try and look up the caller's code object. + i::Address caller = *reinterpret_cast(return_addr_location); - if (foo_ptr != NULL && code == (*foo_ptr)->code()) - ++foo_entry_count; + // Count the invocation. + SymbolInfo* caller_symbol = FindSymbolForAddr(caller); + SymbolInfo* function_symbol = + FindSymbolForAddr(reinterpret_cast(function)); + ++invocations_[std::make_pair(caller_symbol, function_symbol)]; - // Let's check whether bar is the caller. - if (bar_ptr != NULL) { - const v8::internal::byte* caller = - *reinterpret_cast(return_addr_location); + if (!bar_func_.is_null() && function_code == bar_func_->code()) { + // Check that we have a symbol for the "bar" function at the right location. + SymbolLocationMap::iterator it( + symbol_locations_.find(function_code->instruction_start())); + CHECK(it != symbol_locations_.end()); + } - if ((*bar_ptr)->code()->instruction_start() <= caller && - (*bar_ptr)->code()->instruction_end() > caller) { - ++bar_caller_count; - } + if (!foo_func_.is_null() && function_code == foo_func_->code()) { + // Check that we have a symbol for "foo" at the right location. + SymbolLocationMap::iterator it( + symbol_locations_.find(function_code->instruction_start())); + CHECK(it != symbol_locations_.end()); } } -static void RunLoopInNewEnv() { - bar_ptr = NULL; - foo_ptr = NULL; +SymbolInfo* SetFunctionEntryHookTest::FindSymbolForAddr(i::Address addr) { + SymbolLocationMap::iterator it(symbol_locations_.lower_bound(addr)); + // Do we have a direct hit on a symbol? + if (it != symbol_locations_.end()) { + if (it->first == addr) + return it->second; + } + + // If not a direct hit, it'll have to be the previous symbol. + if (it == symbol_locations_.begin()) + return NULL; - v8::Isolate* isolate = v8::Isolate::GetCurrent(); + --it; + size_t offs = addr - it->first; + if (offs < it->second->size) + return it->second; + + return NULL; +} + + +int SetFunctionEntryHookTest::CountInvocations( + const char* caller_name, const char* function_name) { + InvocationMap::iterator it(invocations_.begin()); + int invocations = 0; + for (; it != invocations_.end(); ++it) { + SymbolInfo* caller = it->first.first; + SymbolInfo* function = it->first.second; + + // Filter out non-matching functions. + if (function_name != NULL) { + if (function->name.find(function_name) == std::string::npos) + continue; + } + + // Filter out non-matching callers. + if (caller_name != NULL) { + if (caller == NULL) + continue; + if (caller->name.find(caller_name) == std::string::npos) + continue; + } + + // It matches add the invocation count to the tally. + invocations += it->second; + } + + return invocations; +} + + +void SetFunctionEntryHookTest::RunLoopInNewEnv(v8::Isolate* isolate) { v8::HandleScope outer(isolate); v8::Local env = Context::New(isolate); env->Enter(); + Local t = ObjectTemplate::New(); + t->Set(v8_str("asdf"), v8::FunctionTemplate::New(RuntimeCallback)); + env->Global()->Set(v8_str("obj"), t->NewInstance()); + const char* script = - "function bar() {" - " var sum = 0;" - " for (i = 0; i < 100; ++i)" - " sum = foo(i);" - " return sum;" - "}" - "function foo(i) { return i * i; }"; + "function bar() {\n" + " var sum = 0;\n" + " for (i = 0; i < 100; ++i)\n" + " sum = foo(i);\n" + " return sum;\n" + "}\n" + "function foo(i) { return i * i; }\n" + "// Invoke on the runtime function.\n" + "obj.asdf()"; CompileRun(script); - i::Handle bar = - i::Handle::cast( + bar_func_ = i::Handle::cast( v8::Utils::OpenHandle(*env->Global()->Get(v8_str("bar")))); - ASSERT(*bar); + ASSERT(!bar_func_.is_null()); - i::Handle foo = + foo_func_ = i::Handle::cast( v8::Utils::OpenHandle(*env->Global()->Get(v8_str("foo")))); - ASSERT(*foo); - - bar_ptr = &bar; - foo_ptr = &foo; + ASSERT(!foo_func_.is_null()); v8::Handle value = CompileRun("bar();"); CHECK(value->IsNumber()); @@ -12339,6 +12541,55 @@ static void RunLoopInNewEnv() { env->Exit(); } +void SetFunctionEntryHookTest::RunTest() { + // Work in a new isolate throughout. + v8::Isolate* isolate = v8::Isolate::New(); + + // Test setting the entry hook on the new isolate. + CHECK(v8::V8::SetFunctionEntryHook(isolate, EntryHook)); + + // Replacing the hook, once set should fail. + CHECK_EQ(false, v8::V8::SetFunctionEntryHook(isolate, EntryHook)); + + { + v8::Isolate::Scope scope(isolate); + + v8::V8::SetJitCodeEventHandler(v8::kJitCodeEventDefault, JitEvent); + + RunLoopInNewEnv(isolate); + + // Check the exepected invocation counts. + CHECK_EQ(2, CountInvocations(NULL, "bar")); + CHECK_EQ(200, CountInvocations("bar", "foo")); + CHECK_EQ(200, CountInvocations(NULL, "foo")); + + // Verify that we have an entry hook on some specific stubs. + CHECK_NE(0, CountInvocations(NULL, "CEntryStub")); + CHECK_NE(0, CountInvocations(NULL, "JSEntryStub")); + CHECK_NE(0, CountInvocations(NULL, "JSEntryTrampoline")); + } + isolate->Dispose(); + + Reset(); + + // Make sure a second isolate is unaffected by the previous entry hook. + isolate = v8::Isolate::New(); + { + v8::Isolate::Scope scope(isolate); + + // Reset the entry count to zero and set the entry hook. + RunLoopInNewEnv(isolate); + + // We should record no invocations in this isolate. + CHECK_EQ(0, static_cast(invocations_.size())); + } + // Since the isolate has been used, we shouldn't be able to set an entry + // hook anymore. + CHECK_EQ(false, v8::V8::SetFunctionEntryHook(isolate, EntryHook)); + + isolate->Dispose(); +} + TEST(SetFunctionEntryHook) { // FunctionEntryHook does not work well with experimental natives. @@ -12351,42 +12602,8 @@ TEST(SetFunctionEntryHook) { i::FLAG_allow_natives_syntax = true; i::FLAG_use_inlining = false; - // Test setting and resetting the entry hook. - // Nulling it should always succeed. - CHECK(v8::V8::SetFunctionEntryHook(NULL)); - - CHECK(v8::V8::SetFunctionEntryHook(entry_hook)); - // Setting a hook while one's active should fail. - CHECK_EQ(false, v8::V8::SetFunctionEntryHook(entry_hook)); - - CHECK(v8::V8::SetFunctionEntryHook(NULL)); - - // Reset the entry count to zero and set the entry hook. - bar_entry_count = 0; - bar_caller_count = 0; - foo_entry_count = 0; - CHECK(v8::V8::SetFunctionEntryHook(entry_hook)); - RunLoopInNewEnv(); - - CHECK_EQ(2, bar_entry_count); - CHECK_EQ(200, bar_caller_count); - CHECK_EQ(200, foo_entry_count); - - // Clear the entry hook and count. - bar_entry_count = 0; - bar_caller_count = 0; - foo_entry_count = 0; - v8::V8::SetFunctionEntryHook(NULL); - - // Clear the compilation cache to make sure we don't reuse the - // functions from the previous invocation. - v8::internal::Isolate::Current()->compilation_cache()->Clear(); - - // Verify that entry hooking is now disabled. - RunLoopInNewEnv(); - CHECK_EQ(0u, bar_entry_count); - CHECK_EQ(0u, bar_caller_count); - CHECK_EQ(0u, foo_entry_count); + SetFunctionEntryHookTest test; + test.RunTest(); } @@ -12528,11 +12745,6 @@ static void event_handler(const v8::JitCodeEvent* event) { } -static bool MatchPointers(void* key1, void* key2) { - return key1 == key2; -} - - TEST(SetJitCodeEventHandler) { i::FLAG_stress_compaction = true; i::FLAG_incremental_marking = false; -- 2.7.4