Improved function entry hook coverage

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

Review URL: https://codereview.chromium.org/16578008

Patch from Sigurður Ásgeirsson <siggi@chromium.org>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15361 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
danno@chromium.org 2013-06-27 14:39:37 +00:00
parent f95ddc8891
commit 8494f3bf25
19 changed files with 476 additions and 195 deletions

View File

@ -4476,19 +4476,26 @@ class V8EXPORT V8 {
static void SetReturnAddressLocationResolver( static void SetReturnAddressLocationResolver(
ReturnAddressLocationResolver return_address_resolver); 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 * Allows the host application to provide the address of a function that's
* invoked on entry to every V8-generated function. * invoked on entry to every V8-generated function.
* Note that \p entry_hook is invoked at the very start of each * Note that \p entry_hook is invoked at the very start of each
* generated function. * generated function.
* *
* \param isolate the isolate to operate on.
* \param entry_hook a function that will be invoked on entry to every * \param entry_hook a function that will be invoked on entry to every
* V8-generated function. * V8-generated function.
* \returns true on success on supported platforms, false on failure. * \returns true on success on supported platforms, false on failure.
* \note Setting a new entry hook function when one is already active will * \note Setting an entry hook can only be done very early in an isolates
* fail. * 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 * Allows the host application to provide the address of a function that is

View File

@ -300,8 +300,13 @@ static inline bool EmptyCheck(const char* location, const v8::Data* obj) {
// --- S t a t i c s --- // --- S t a t i c s ---
static bool InitializeHelper() { static bool InitializeHelper(i::Isolate* isolate) {
if (i::Snapshot::Initialize()) return true; // 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); return i::V8::Initialize(NULL);
} }
@ -313,7 +318,7 @@ static inline bool EnsureInitializedForIsolate(i::Isolate* isolate,
if (isolate->IsInitialized()) return true; if (isolate->IsInitialized()) return true;
} }
ASSERT(isolate == i::Isolate::Current()); 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 // Some initializing API functions are called early and may be
@ -5211,7 +5216,7 @@ bool v8::V8::Initialize() {
if (isolate != NULL && isolate->IsInitialized()) { if (isolate != NULL && isolate->IsInitialized()) {
return true; return true;
} }
return InitializeHelper(); return InitializeHelper(isolate);
} }
@ -5227,7 +5232,30 @@ void v8::V8::SetReturnAddressLocationResolver(
bool v8::V8::SetFunctionEntryHook(FunctionEntryHook entry_hook) { 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<i::Isolate*>(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;
} }

View File

@ -717,6 +717,7 @@ static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
// r3: argc // r3: argc
// r4: argv // r4: argv
// r5-r7, cp may be clobbered // r5-r7, cp may be clobbered
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// Clear the context before we push it when entering the internal frame. // Clear the context before we push it when entering the internal frame.
__ mov(cp, Operand::Zero()); __ mov(cp, Operand::Zero());

View File

@ -3181,6 +3181,8 @@ void CEntryStub::Generate(MacroAssembler* masm) {
// sp: stack pointer (restored as callee's sp after C call) // sp: stack pointer (restored as callee's sp after C call)
// cp: current context (C callee-saved) // cp: current context (C callee-saved)
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// Result returned in r0 or r0+r1 by default. // Result returned in r0 or r0+r1 by default.
// NOTE: Invocations of builtins may return failure objects // 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; Label invoke, handler_entry, exit;
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// Called from C, so do not pop argc and args on exit (preserve sp) // Called from C, so do not pop argc and args on exit (preserve sp)
// No need to save register-passed args // No need to save register-passed args
// Save callee-saved registers (incl. cp and fp), sp, and lr // Save callee-saved registers (incl. cp and fp), sp, and lr
@ -7071,8 +7075,9 @@ void StubFailureTrampolineStub::Generate(MacroAssembler* masm) {
void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) { void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) {
if (entry_hook_ != NULL) { if (masm->isolate()->function_entry_hook() != NULL) {
PredictableCodeSizeScope predictable(masm, 4 * Assembler::kInstrSize); PredictableCodeSizeScope predictable(masm, 4 * Assembler::kInstrSize);
AllowStubCallsScope allow_stub_calls(masm, true);
ProfileEntryHookStub stub; ProfileEntryHookStub stub;
__ push(lr); __ push(lr);
__ CallStub(&stub); __ CallStub(&stub);
@ -7086,9 +7091,21 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
const int32_t kReturnAddressDistanceFromFunctionStart = const int32_t kReturnAddressDistanceFromFunctionStart =
3 * Assembler::kInstrSize; 3 * Assembler::kInstrSize;
// Save live volatile registers. // This should contain all kCallerSaved registers.
__ Push(lr, r5, r1); const RegList kSavedRegs =
const int32_t kNumSavedRegs = 3; 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. // Compute the function's address for the first argument.
__ sub(r0, lr, Operand(kReturnAddressDistanceFromFunctionStart)); __ sub(r0, lr, Operand(kReturnAddressDistanceFromFunctionStart));
@ -7106,14 +7123,12 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
} }
#if defined(V8_HOST_ARCH_ARM) #if defined(V8_HOST_ARCH_ARM)
__ mov(ip, Operand(reinterpret_cast<int32_t>(&entry_hook_))); __ mov(ip, FUNCTION_ADDR(masm->isolate()->function_entry_hook()),
__ ldr(ip, MemOperand(ip)); RelocInfo::NONE));
#else #else
// Under the simulator we need to indirect the entry hook through a // Under the simulator we need to indirect the entry hook through a
// trampoline function at a known address. // trampoline function at a known address.
Address trampoline_address = reinterpret_cast<Address>( ApiFunction dispatcher(FUNCTION_ADDR(EntryHookTrampoline));
reinterpret_cast<intptr_t>(EntryHookTrampoline));
ApiFunction dispatcher(trampoline_address);
__ mov(ip, Operand(ExternalReference(&dispatcher, __ mov(ip, Operand(ExternalReference(&dispatcher,
ExternalReference::BUILTIN_CALL, ExternalReference::BUILTIN_CALL,
masm->isolate()))); masm->isolate())));
@ -7125,8 +7140,8 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
__ mov(sp, r5); __ mov(sp, r5);
} }
__ Pop(lr, r5, r1); // Also pop pc to get Ret(0).
__ Ret(); __ ldm(ia_w, sp, kSavedRegs | pc.bit());
} }

View File

@ -2575,7 +2575,14 @@ Genesis::Genesis(Isolate* isolate,
StackLimitCheck check(isolate); StackLimitCheck check(isolate);
if (check.HasOverflowed()) return; 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<Context>();
}
if (!native_context().is_null()) { if (!native_context().is_null()) {
AddToWeakNativeContextList(*native_context()); AddToWeakNativeContextList(*native_context());
isolate->set_context(*native_context()); isolate->set_context(*native_context());

View File

@ -757,24 +757,11 @@ void StubFailureTrampolineStub::GenerateAheadOfTime(Isolate* isolate) {
} }
FunctionEntryHook ProfileEntryHookStub::entry_hook_ = NULL;
void ProfileEntryHookStub::EntryHookTrampoline(intptr_t function, void ProfileEntryHookStub::EntryHookTrampoline(intptr_t function,
intptr_t stack_pointer) { intptr_t stack_pointer) {
if (entry_hook_ != NULL) FunctionEntryHook entry_hook = Isolate::Current()->function_entry_hook();
entry_hook_(function, stack_pointer); ASSERT(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;
} }

View File

@ -2135,13 +2135,6 @@ class ProfileEntryHookStub : public PlatformCodeStub {
// Generates a call to the entry hook if it's enabled. // Generates a call to the entry hook if it's enabled.
static void MaybeCallEntryHook(MacroAssembler* masm); 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: private:
static void EntryHookTrampoline(intptr_t function, static void EntryHookTrampoline(intptr_t function,
intptr_t stack_pointer); intptr_t stack_pointer);
@ -2151,9 +2144,6 @@ class ProfileEntryHookStub : public PlatformCodeStub {
void Generate(MacroAssembler* masm); void Generate(MacroAssembler* masm);
// The current function entry hook.
static FunctionEntryHook entry_hook_;
DISALLOW_COPY_AND_ASSIGN(ProfileEntryHookStub); DISALLOW_COPY_AND_ASSIGN(ProfileEntryHookStub);
}; };

View File

@ -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<Address*>(
return_address_location_resolver_(
reinterpret_cast<uintptr_t>(pc_address)));
}
}
inline EntryFrame::EntryFrame(StackFrameIteratorBase* iterator) inline EntryFrame::EntryFrame(StackFrameIteratorBase* iterator)
: StackFrame(iterator) { : StackFrame(iterator) {
} }

View File

@ -43,19 +43,8 @@ namespace v8 {
namespace internal { namespace internal {
static ReturnAddressLocationResolver return_address_location_resolver = NULL; ReturnAddressLocationResolver
StackFrame::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<Address*>(
return_address_location_resolver(
reinterpret_cast<uintptr_t>(pc_address)));
}
}
// Iterator that supports traversing the stack handlers of a // Iterator that supports traversing the stack handlers of a
@ -239,7 +228,7 @@ SafeStackFrameIterator::SafeStackFrameIterator(
ASSERT(fp != NULL); ASSERT(fp != NULL);
state.fp = fp; state.fp = fp;
state.sp = sp; state.sp = sp;
state.pc_address = ResolveReturnAddressLocation( state.pc_address = StackFrame::ResolveReturnAddressLocation(
reinterpret_cast<Address*>(StandardFrame::ComputePCAddress(fp))); reinterpret_cast<Address*>(StandardFrame::ComputePCAddress(fp)));
type = StackFrame::ComputeType(this, &state); type = StackFrame::ComputeType(this, &state);
} else { } else {
@ -389,8 +378,8 @@ void StackFrame::IteratePc(ObjectVisitor* v,
void StackFrame::SetReturnAddressLocationResolver( void StackFrame::SetReturnAddressLocationResolver(
ReturnAddressLocationResolver resolver) { ReturnAddressLocationResolver resolver) {
ASSERT(return_address_location_resolver == NULL); ASSERT(return_address_location_resolver_ == NULL);
return_address_location_resolver = resolver; return_address_location_resolver_ = resolver;
} }

View File

@ -297,6 +297,10 @@ class StackFrame BASE_EMBEDDED {
static void SetReturnAddressLocationResolver( static void SetReturnAddressLocationResolver(
ReturnAddressLocationResolver resolver); ReturnAddressLocationResolver resolver);
// Resolves pc_address through the resolution address function if one is set.
static inline Address* ResolveReturnAddressLocation(Address* pc_address);
// Printing support. // Printing support.
enum PrintMode { OVERVIEW, DETAILS }; enum PrintMode { OVERVIEW, DETAILS };
virtual void Print(StringStream* accumulator, virtual void Print(StringStream* accumulator,
@ -332,6 +336,8 @@ class StackFrame BASE_EMBEDDED {
Isolate* isolate_; Isolate* isolate_;
State state_; State state_;
static ReturnAddressLocationResolver return_address_location_resolver_;
// Fill in the state of the calling frame. // Fill in the state of the calling frame.
virtual void ComputeCallerState(State* state) const = 0; virtual void ComputeCallerState(State* state) const = 0;

View File

@ -447,6 +447,8 @@ void Builtins::Generate_JSConstructStubApi(MacroAssembler* masm) {
static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
bool is_construct) { bool is_construct) {
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// Clear the context before we push it when entering the internal frame. // Clear the context before we push it when entering the internal frame.
__ Set(esi, Immediate(0)); __ Set(esi, Immediate(0));

View File

@ -5077,6 +5077,8 @@ void CEntryStub::Generate(MacroAssembler* masm) {
// esi: current context (C callee-saved) // esi: current context (C callee-saved)
// edi: JS function of the caller (C callee-saved) // edi: JS function of the caller (C callee-saved)
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// NOTE: Invocations of builtins may return failure objects instead // NOTE: Invocations of builtins may return failure objects instead
// of a proper result. The builtin entry handles this by performing // of a proper result. The builtin entry handles this by performing
// a garbage collection and retrying the builtin (twice). // a garbage collection and retrying the builtin (twice).
@ -5150,6 +5152,8 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) {
Label invoke, handler_entry, exit; Label invoke, handler_entry, exit;
Label not_outermost_js, not_outermost_js_2; Label not_outermost_js, not_outermost_js_2;
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// Set up frame. // Set up frame.
__ push(ebp); __ push(ebp);
__ mov(ebp, esp); __ mov(ebp, esp);
@ -7691,7 +7695,11 @@ void StubFailureTrampolineStub::Generate(MacroAssembler* masm) {
void ProfileEntryHookStub::MaybeCallEntryHook(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; ProfileEntryHookStub stub;
masm->CallStub(&stub); masm->CallStub(&stub);
} }
@ -7699,9 +7707,11 @@ void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) {
void ProfileEntryHookStub::Generate(MacroAssembler* masm) { void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
// Ecx is the only volatile register we must save. // Save volatile registers.
const int kNumSavedRegisters = 1; const int kNumSavedRegisters = 3;
__ push(eax);
__ push(ecx); __ push(ecx);
__ push(edx);
// Calculate and push the original stack pointer. // Calculate and push the original stack pointer.
__ lea(eax, Operand(esp, (kNumSavedRegisters + 1) * kPointerSize)); __ lea(eax, Operand(esp, (kNumSavedRegisters + 1) * kPointerSize));
@ -7714,12 +7724,16 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
__ push(eax); __ push(eax);
// Call the entry hook. // Call the entry hook.
int32_t hook_location = reinterpret_cast<int32_t>(&entry_hook_); ASSERT(masm->isolate()->function_entry_hook() != NULL);
__ call(Operand(hook_location, RelocInfo::NONE32)); __ call(FUNCTION_ADDR(masm->isolate()->function_entry_hook()),
RelocInfo::RUNTIME_ENTRY);
__ add(esp, Immediate(2 * kPointerSize)); __ add(esp, Immediate(2 * kPointerSize));
// Restore ecx. // Restore ecx.
__ pop(edx);
__ pop(ecx); __ pop(ecx);
__ pop(eax);
__ ret(0); __ ret(0);
} }

View File

@ -144,7 +144,7 @@ IC::IC(FrameDepth depth, Isolate* isolate) : isolate_(isolate) {
ASSERT(fp == frame->fp() && pc_address == frame->pc_address()); ASSERT(fp == frame->fp() && pc_address == frame->pc_address());
#endif #endif
fp_ = fp; fp_ = fp;
pc_address_ = pc_address; pc_address_ = StackFrame::ResolveReturnAddressLocation(pc_address);
} }

View File

@ -1753,8 +1753,10 @@ Isolate::Isolate()
date_cache_(NULL), date_cache_(NULL),
code_stub_interface_descriptors_(NULL), code_stub_interface_descriptors_(NULL),
context_exit_happened_(false), context_exit_happened_(false),
initialized_from_snapshot_(false),
cpu_profiler_(NULL), cpu_profiler_(NULL),
heap_profiler_(NULL), heap_profiler_(NULL),
function_entry_hook_(NULL),
deferred_handles_head_(NULL), deferred_handles_head_(NULL),
optimizing_compiler_thread_(this), optimizing_compiler_thread_(this),
marking_thread_(NULL), marking_thread_(NULL),
@ -2077,6 +2079,14 @@ bool Isolate::Init(Deserializer* des) {
ASSERT(Isolate::Current() == this); ASSERT(Isolate::Current() == this);
TRACE_ISOLATE(init); 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. // The initialization process does not handle memory exhaustion.
DisallowAllocationFailure disallow_allocation_failure; DisallowAllocationFailure disallow_allocation_failure;
@ -2268,6 +2278,9 @@ bool Isolate::Init(Deserializer* des) {
sweeper_thread_[i]->Start(); sweeper_thread_[i]->Start();
} }
} }
initialized_from_snapshot_ = (des != NULL);
return true; return true;
} }

View File

@ -547,7 +547,7 @@ class Isolate {
} }
Context** context_address() { return &thread_local_top_.context_; } 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) { void set_save_context(SaveContext* save) {
thread_local_top_.save_context_ = save; thread_local_top_.save_context_ = save;
} }
@ -1050,6 +1050,8 @@ class Isolate {
context_exit_happened_ = context_exit_happened; context_exit_happened_ = context_exit_happened;
} }
bool initialized_from_snapshot() { return initialized_from_snapshot_; }
double time_millis_since_init() { double time_millis_since_init() {
return OS::TimeCurrentMillis() - time_millis_at_init_; return OS::TimeCurrentMillis() - time_millis_at_init_;
} }
@ -1109,6 +1111,11 @@ class Isolate {
HStatistics* GetHStatistics(); HStatistics* GetHStatistics();
HTracer* GetHTracer(); 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: private:
Isolate(); Isolate();
@ -1288,6 +1295,9 @@ class Isolate {
// that a context was recently exited. // that a context was recently exited.
bool context_exit_happened_; bool context_exit_happened_;
// True if this isolate was initialized from a snapshot.
bool initialized_from_snapshot_;
// Time stamp at initialization. // Time stamp at initialization.
double time_millis_at_init_; double time_millis_at_init_;
@ -1311,6 +1321,7 @@ class Isolate {
#endif #endif
CpuProfiler* cpu_profiler_; CpuProfiler* cpu_profiler_;
HeapProfiler* heap_profiler_; HeapProfiler* heap_profiler_;
FunctionEntryHook function_entry_hook_;
#define GLOBAL_BACKING_STORE(type, name, initialvalue) \ #define GLOBAL_BACKING_STORE(type, name, initialvalue) \
type name##_; type name##_;

View File

@ -101,6 +101,8 @@ class V8 : public AllStatic {
// Support for return-address rewriting profilers. // Support for return-address rewriting profilers.
static void SetReturnAddressLocationResolver( static void SetReturnAddressLocationResolver(
ReturnAddressLocationResolver resolver); ReturnAddressLocationResolver resolver);
// Support for entry hooking JITed code.
static void SetFunctionEntryHook(FunctionEntryHook entry_hook);
// Random number generation support. Not cryptographically safe. // Random number generation support. Not cryptographically safe.
static uint32_t Random(Context* context); static uint32_t Random(Context* context);
// We use random numbers internally in memory allocation and in the // We use random numbers internally in memory allocation and in the

View File

@ -456,6 +456,8 @@ void Builtins::Generate_JSConstructStubApi(MacroAssembler* masm) {
static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
bool is_construct) { bool is_construct) {
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// Expects five C++ function parameters. // Expects five C++ function parameters.
// - Address entry (ignored) // - Address entry (ignored)
// - JSFunction* function ( // - JSFunction* function (

View File

@ -4129,6 +4129,8 @@ void CEntryStub::Generate(MacroAssembler* masm) {
// this by performing a garbage collection and retrying the // this by performing a garbage collection and retrying the
// builtin once. // builtin once.
ProfileEntryHookStub::MaybeCallEntryHook(masm);
// Enter the exit frame that transitions from JavaScript to C++. // Enter the exit frame that transitions from JavaScript to C++.
#ifdef _WIN64 #ifdef _WIN64
int arg_stack_space = (result_size_ < 2 ? 2 : 4); int arg_stack_space = (result_size_ < 2 ? 2 : 4);
@ -4209,6 +4211,8 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) {
Label invoke, handler_entry, exit; Label invoke, handler_entry, exit;
Label not_outermost_js, not_outermost_js_2; Label not_outermost_js, not_outermost_js_2;
ProfileEntryHookStub::MaybeCallEntryHook(masm);
{ // NOLINT. Scope block confuses linter. { // NOLINT. Scope block confuses linter.
MacroAssembler::NoRootArrayScope uninitialized_root_register(masm); MacroAssembler::NoRootArrayScope uninitialized_root_register(masm);
// Set up frame. // Set up frame.
@ -6663,7 +6667,11 @@ void StubFailureTrampolineStub::Generate(MacroAssembler* masm) {
void ProfileEntryHookStub::MaybeCallEntryHook(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; ProfileEntryHookStub stub;
masm->CallStub(&stub); masm->CallStub(&stub);
} }
@ -6671,45 +6679,25 @@ void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) {
void ProfileEntryHookStub::Generate(MacroAssembler* masm) { void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
// Save volatile registers. // This stub can be called from essentially anywhere, so it needs to save
// Live registers at this point are the same as at the start of any // all volatile and callee-save registers.
// JS function: const size_t kNumSavedRegisters = 2;
// o rdi: the JS function object being called (i.e. ourselves) __ push(arg_reg_1);
// o rsi: our context __ push(arg_reg_2);
// 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
// Calculate the original stack pointer and store it in the second arg. // Calculate the original stack pointer and store it in the second arg.
#ifdef _WIN64 __ lea(arg_reg_2, Operand(rsp, (kNumSavedRegisters + 1) * kPointerSize));
__ lea(rdx, Operand(rsp, (kNumSavedRegisters + 1) * kPointerSize));
#else
__ lea(rsi, Operand(rsp, (kNumSavedRegisters + 1) * kPointerSize));
#endif
// Calculate the function address to the first arg. // Calculate the function address to the first arg.
#ifdef _WIN64 __ movq(arg_reg_1, Operand(rsp, kNumSavedRegisters * kPointerSize));
__ movq(rcx, Operand(rsp, kNumSavedRegisters * kPointerSize)); __ subq(arg_reg_1, Immediate(Assembler::kShortCallInstructionLength));
__ subq(rcx, Immediate(Assembler::kShortCallInstructionLength));
#else // Save the remainder of the volatile registers.
__ movq(rdi, Operand(rsp, kNumSavedRegisters * kPointerSize)); masm->PushCallerSaved(kSaveFPRegs, arg_reg_1, arg_reg_2);
__ subq(rdi, Immediate(Assembler::kShortCallInstructionLength));
#endif
// Call the entry hook function. // Call the entry hook function.
__ movq(rax, &entry_hook_, RelocInfo::NONE64); __ movq(rax, FUNCTION_ADDR(masm->isolate()->function_entry_hook()),
__ movq(rax, Operand(rax, 0)); RelocInfo::NONE64);
AllowExternalCallThatCantCauseGC scope(masm); AllowExternalCallThatCantCauseGC scope(masm);
@ -6718,13 +6706,9 @@ void ProfileEntryHookStub::Generate(MacroAssembler* masm) {
__ CallCFunction(rax, kArgumentCount); __ CallCFunction(rax, kArgumentCount);
// Restore volatile regs. // Restore volatile regs.
#ifdef _WIN64 masm->PopCallerSaved(kSaveFPRegs, arg_reg_1, arg_reg_2);
__ pop(rcx); __ pop(arg_reg_2);
#else __ pop(arg_reg_1);
__ pop(rsi);
__ pop(rdi);
__ pop(rcx);
#endif
__ Ret(); __ Ret();
} }

382
test/cctest/test-api.cc Executable file → Normal file
View File

@ -31,6 +31,8 @@
#include <signal.h> // kill #include <signal.h> // kill
#include <unistd.h> // getpid #include <unistd.h> // getpid
#endif // WIN32 #endif // WIN32
#include <string>
#include <map>
#include "v8.h" #include "v8.h"
@ -12263,68 +12265,268 @@ THREADED_TEST(NestedHandleScopeAndContexts) {
} }
static i::Handle<i::JSFunction>* foo_ptr = NULL; static bool MatchPointers(void* key1, void* key2) {
static int foo_entry_count = 0; return key1 == key2;
static i::Handle<i::JSFunction>* bar_ptr = NULL; }
static int bar_entry_count = 0;
static int bar_caller_count = 0;
static void entry_hook(uintptr_t function, struct SymbolInfo {
uintptr_t return_addr_location) { size_t id;
i::Code* code = i::Code::GetCodeFromTargetAddress( size_t size;
reinterpret_cast<i::Address>(function)); std::string name;
CHECK(code != NULL); };
if (bar_ptr != NULL && code == (*bar_ptr)->code())
++bar_entry_count;
if (foo_ptr != NULL && code == (*foo_ptr)->code()) class SetFunctionEntryHookTest {
++foo_entry_count; 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);
}
// Let's check whether bar is the caller. void OnEntryHook(uintptr_t function,
if (bar_ptr != NULL) { uintptr_t return_addr_location);
const v8::internal::byte* caller = static void EntryHook(uintptr_t function,
*reinterpret_cast<v8::internal::byte**>(return_addr_location); uintptr_t return_addr_location) {
CHECK(instance_ != NULL);
instance_->OnEntryHook(function, return_addr_location);
}
if ((*bar_ptr)->code()->instruction_start() <= caller && static void RuntimeCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
(*bar_ptr)->code()->instruction_end() > caller) { CHECK(instance_ != NULL);
++bar_caller_count; 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.
size_t CountInvocations(const char* caller_name,
const char* function_name);
i::Handle<i::JSFunction> foo_func_;
i::Handle<i::JSFunction> bar_func_;
typedef std::map<size_t, SymbolInfo> SymbolMap;
typedef std::map<i::Address, SymbolInfo*> SymbolLocationMap;
typedef std::map<std::pair<SymbolInfo*, SymbolInfo*>, size_t> 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);
} }
} }
static void RunLoopInNewEnv() { void SetFunctionEntryHookTest::OnJitEvent(const v8::JitCodeEvent* event) {
bar_ptr = NULL; switch (event->type) {
foo_ptr = NULL; case v8::JitCodeEvent::CODE_ADDED: {
CHECK(event->code_start != NULL);
CHECK_NE(0, static_cast<int>(event->code_len));
CHECK(event->name.str != NULL);
size_t symbol_id = symbols_.size();
v8::Isolate* isolate = v8::Isolate::GetCurrent(); // 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<i::Address>(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<i::Address>(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<i::Address>(event->new_code_start),
info);
}
}
default:
break;
}
}
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<i::Address>(function));
CHECK(function_code != NULL);
// Then try and look up the caller's code object.
i::Address caller = *reinterpret_cast<i::Address*>(return_addr_location);
// Count the invocation.
SymbolInfo* caller_symbol = FindSymbolForAddr(caller);
SymbolInfo* function_symbol =
FindSymbolForAddr(reinterpret_cast<i::Address>(function));
++invocations_[std::make_pair(caller_symbol, function_symbol)];
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 (!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());
}
}
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;
--it;
size_t offs = addr - it->first;
if (offs < it->second->size)
return it->second;
return NULL;
}
size_t SetFunctionEntryHookTest::CountInvocations(
const char* caller_name, const char* function_name) {
InvocationMap::iterator it(invocations_.begin());
size_t 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::HandleScope outer(isolate);
v8::Local<Context> env = Context::New(isolate); v8::Local<Context> env = Context::New(isolate);
env->Enter(); env->Enter();
const char* script = Local<ObjectTemplate> t = ObjectTemplate::New();
"function bar() {" t->Set(v8_str("asdf"), v8::FunctionTemplate::New(RuntimeCallback));
" var sum = 0;" env->Global()->Set(v8_str("obj"), t->NewInstance());
" for (i = 0; i < 100; ++i)"
" sum = foo(i);"
" return sum;"
"}"
"function foo(i) { return i * i; }";
CompileRun(script);
i::Handle<i::JSFunction> bar =
i::Handle<i::JSFunction>::cast(
v8::Utils::OpenHandle(*env->Global()->Get(v8_str("bar"))));
ASSERT(*bar);
i::Handle<i::JSFunction> foo = const char* script =
"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);
bar_func_ = i::Handle<i::JSFunction>::cast(
v8::Utils::OpenHandle(*env->Global()->Get(v8_str("bar"))));
ASSERT(!bar_func_.is_null());
foo_func_ =
i::Handle<i::JSFunction>::cast( i::Handle<i::JSFunction>::cast(
v8::Utils::OpenHandle(*env->Global()->Get(v8_str("foo")))); v8::Utils::OpenHandle(*env->Global()->Get(v8_str("foo"))));
ASSERT(*foo); ASSERT(!foo_func_.is_null());
bar_ptr = &bar;
foo_ptr = &foo;
v8::Handle<v8::Value> value = CompileRun("bar();"); v8::Handle<v8::Value> value = CompileRun("bar();");
CHECK(value->IsNumber()); CHECK(value->IsNumber());
@ -12339,6 +12541,55 @@ static void RunLoopInNewEnv() {
env->Exit(); 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, 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) { TEST(SetFunctionEntryHook) {
// FunctionEntryHook does not work well with experimental natives. // FunctionEntryHook does not work well with experimental natives.
@ -12351,42 +12602,8 @@ TEST(SetFunctionEntryHook) {
i::FLAG_allow_natives_syntax = true; i::FLAG_allow_natives_syntax = true;
i::FLAG_use_inlining = false; i::FLAG_use_inlining = false;
// Test setting and resetting the entry hook. SetFunctionEntryHookTest test;
// Nulling it should always succeed. test.RunTest();
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);
} }
@ -12528,11 +12745,6 @@ static void event_handler(const v8::JitCodeEvent* event) {
} }
static bool MatchPointers(void* key1, void* key2) {
return key1 == key2;
}
TEST(SetJitCodeEventHandler) { TEST(SetJitCodeEventHandler) {
i::FLAG_stress_compaction = true; i::FLAG_stress_compaction = true;
i::FLAG_incremental_marking = false; i::FLAG_incremental_marking = false;