CPU profiler: add secure profiles by filtering out functions using security tokens.

As several pages can run in a single V8 instance, it is possible to
have functions from different security contexts intermixed in a single
CPU profile.  To avoid exposing function names from one page to
another, filtering is introduced.

The basic idea is that instead of capturing return addresses from
stack, we're now capturing JSFunction addresses (as we anyway work
only with JS stack frames.)  Each JSFunction can reach out for
context's security token. When providing a profile to a page, the
profile is filtered using the security token of caller page. Any
functions with different security tokens are filtered out (yes, we
only do fast path check for now) and their ticks are attributed to
their parents.

Review URL: http://codereview.chromium.org/2083005

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4673 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 73b2fc29
......@@ -139,6 +139,15 @@ class V8EXPORT CpuProfile {
*/
class V8EXPORT CpuProfiler {
public:
/**
* A note on security tokens usage. As scripts from different
* origins can run inside a single V8 instance, it is possible to
* have functions from different security contexts intermixed in a
* single CPU profile. To avoid exposing function names belonging to
* other contexts, filtering by security token is performed while
* obtaining profiling results.
*/
/**
* Returns the number of profiles collected (doesn't include
* profiles that are being collected at the moment of call.)
......@@ -146,16 +155,22 @@ class V8EXPORT CpuProfiler {
static int GetProfilesCount();
/** Returns a profile by index. */
static const CpuProfile* GetProfile(int index);
static const CpuProfile* GetProfile(
int index,
Handle<Value> security_token = Handle<Value>());
/** Returns a profile by uid. */
static const CpuProfile* FindProfile(unsigned uid);
static const CpuProfile* FindProfile(
unsigned uid,
Handle<Value> security_token = Handle<Value>());
/**
* Starts collecting CPU profile. Title may be an empty string. It
* is allowed to have several profiles being collected at
* once. Attempts to start collecting several profiles with the same
* title are silently ignored.
* title are silently ignored. While collecting a profile, functions
* from all security contexts are included in it. The token-based
* filtering is only performed when querying for a profile.
*/
static void StartProfiling(Handle<String> title);
......@@ -163,7 +178,9 @@ class V8EXPORT CpuProfiler {
* Stops collecting CPU profile with a given title and returns it.
* If the title given is empty, finishes the last profile started.
*/
static const CpuProfile* StopProfiling(Handle<String> title);
static const CpuProfile* StopProfiling(
Handle<String> title,
Handle<Value> security_token = Handle<Value>());
};
......
......@@ -4250,15 +4250,23 @@ int CpuProfiler::GetProfilesCount() {
}
const CpuProfile* CpuProfiler::GetProfile(int index) {
const CpuProfile* CpuProfiler::GetProfile(int index,
Handle<Value> security_token) {
IsDeadCheck("v8::CpuProfiler::GetProfile");
return reinterpret_cast<const CpuProfile*>(i::CpuProfiler::GetProfile(index));
return reinterpret_cast<const CpuProfile*>(
i::CpuProfiler::GetProfile(
security_token.IsEmpty() ? NULL : *Utils::OpenHandle(*security_token),
index));
}
const CpuProfile* CpuProfiler::FindProfile(unsigned uid) {
const CpuProfile* CpuProfiler::FindProfile(unsigned uid,
Handle<Value> security_token) {
IsDeadCheck("v8::CpuProfiler::FindProfile");
return reinterpret_cast<const CpuProfile*>(i::CpuProfiler::FindProfile(uid));
return reinterpret_cast<const CpuProfile*>(
i::CpuProfiler::FindProfile(
security_token.IsEmpty() ? NULL : *Utils::OpenHandle(*security_token),
uid));
}
......@@ -4268,10 +4276,13 @@ void CpuProfiler::StartProfiling(Handle<String> title) {
}
const CpuProfile* CpuProfiler::StopProfiling(Handle<String> title) {
const CpuProfile* CpuProfiler::StopProfiling(Handle<String> title,
Handle<Value> security_token) {
IsDeadCheck("v8::CpuProfiler::StopProfiling");
return reinterpret_cast<const CpuProfile*>(
i::CpuProfiler::StopProfiling(*Utils::OpenHandle(*title)));
i::CpuProfiler::StopProfiling(
security_token.IsEmpty() ? NULL : *Utils::OpenHandle(*security_token),
*Utils::OpenHandle(*title)));
}
#endif // ENABLE_LOGGING_AND_PROFILING
......
......@@ -54,7 +54,7 @@ void CodeDeleteEventRecord::UpdateCodeMap(CodeMap* code_map) {
void CodeAliasEventRecord::UpdateCodeMap(CodeMap* code_map) {
code_map->AddAlias(alias, start);
code_map->AddAlias(start, entry, code_start);
}
......
......@@ -141,13 +141,15 @@ void ProfilerEventsProcessor::CodeDeleteEvent(Address from) {
void ProfilerEventsProcessor::FunctionCreateEvent(Address alias,
Address start) {
Address start,
int security_token_id) {
CodeEventsContainer evt_rec;
CodeAliasEventRecord* rec = &evt_rec.CodeAliasEventRecord_;
rec->type = CodeEventRecord::CODE_ALIAS;
rec->order = ++enqueue_order_;
rec->alias = alias;
rec->start = start;
rec->start = alias;
rec->entry = generator_->NewCodeEntry(security_token_id);
rec->code_start = start;
events_buffer_.Enqueue(evt_rec);
}
......@@ -257,26 +259,30 @@ CpuProfile* CpuProfiler::StopProfiling(const char* title) {
}
CpuProfile* CpuProfiler::StopProfiling(String* title) {
return is_profiling() ? singleton_->StopCollectingProfile(title) : NULL;
CpuProfile* CpuProfiler::StopProfiling(Object* security_token, String* title) {
return is_profiling() ?
singleton_->StopCollectingProfile(security_token, title) : NULL;
}
int CpuProfiler::GetProfilesCount() {
ASSERT(singleton_ != NULL);
return singleton_->profiles_->profiles()->length();
// The count of profiles doesn't depend on a security token.
return singleton_->profiles_->Profiles(CodeEntry::kNoSecurityToken)->length();
}
CpuProfile* CpuProfiler::GetProfile(int index) {
CpuProfile* CpuProfiler::GetProfile(Object* security_token, int index) {
ASSERT(singleton_ != NULL);
return singleton_->profiles_->profiles()->at(index);
const int token = singleton_->token_enumerator_->GetTokenId(security_token);
return singleton_->profiles_->Profiles(token)->at(index);
}
CpuProfile* CpuProfiler::FindProfile(unsigned uid) {
CpuProfile* CpuProfiler::FindProfile(Object* security_token, unsigned uid) {
ASSERT(singleton_ != NULL);
return singleton_->profiles_->GetProfile(uid);
const int token = singleton_->token_enumerator_->GetTokenId(security_token);
return singleton_->profiles_->GetProfile(token, uid);
}
......@@ -348,8 +354,15 @@ void CpuProfiler::CodeDeleteEvent(Address from) {
void CpuProfiler::FunctionCreateEvent(JSFunction* function) {
int security_token_id = CodeEntry::kNoSecurityToken;
if (function->unchecked_context()->IsContext()) {
security_token_id = singleton_->token_enumerator_->GetTokenId(
function->context()->global_context()->security_token());
}
singleton_->processor_->FunctionCreateEvent(
function->address(), function->code()->address());
function->address(),
function->code()->address(),
security_token_id);
}
......@@ -388,12 +401,14 @@ void CpuProfiler::SetterCallbackEvent(String* name, Address entry_point) {
CpuProfiler::CpuProfiler()
: profiles_(new CpuProfilesCollection()),
next_profile_uid_(1),
token_enumerator_(new TokenEnumerator()),
generator_(NULL),
processor_(NULL) {
}
CpuProfiler::~CpuProfiler() {
delete token_enumerator_;
delete profiles_;
}
......@@ -438,7 +453,9 @@ void CpuProfiler::StartProcessorIfNotStarted() {
CpuProfile* CpuProfiler::StopCollectingProfile(const char* title) {
const double actual_sampling_rate = generator_->actual_sampling_rate();
StopProcessorIfLastProfile();
CpuProfile* result = profiles_->StopProfiling(title, actual_sampling_rate);
CpuProfile* result = profiles_->StopProfiling(CodeEntry::kNoSecurityToken,
title,
actual_sampling_rate);
if (result != NULL) {
result->Print();
}
......@@ -446,10 +463,12 @@ CpuProfile* CpuProfiler::StopCollectingProfile(const char* title) {
}
CpuProfile* CpuProfiler::StopCollectingProfile(String* title) {
CpuProfile* CpuProfiler::StopCollectingProfile(Object* security_token,
String* title) {
const double actual_sampling_rate = generator_->actual_sampling_rate();
StopProcessorIfLastProfile();
return profiles_->StopProfiling(title, actual_sampling_rate);
int token = token_enumerator_->GetTokenId(security_token);
return profiles_->StopProfiling(token, title, actual_sampling_rate);
}
......
......@@ -41,7 +41,7 @@ class CodeMap;
class CpuProfile;
class CpuProfilesCollection;
class ProfileGenerator;
class TokenEnumerator;
#define CODE_EVENTS_TYPE_LIST(V) \
V(CODE_CREATION, CodeCreateEventRecord) \
......@@ -94,8 +94,9 @@ class CodeDeleteEventRecord : public CodeEventRecord {
class CodeAliasEventRecord : public CodeEventRecord {
public:
Address alias;
Address start;
CodeEntry* entry;
Address code_start;
INLINE(void UpdateCodeMap(CodeMap* code_map));
};
......@@ -151,7 +152,7 @@ class ProfilerEventsProcessor : public Thread {
Address start, unsigned size);
void CodeMoveEvent(Address from, Address to);
void CodeDeleteEvent(Address from);
void FunctionCreateEvent(Address alias, Address start);
void FunctionCreateEvent(Address alias, Address start, int security_token_id);
void FunctionMoveEvent(Address from, Address to);
void FunctionDeleteEvent(Address from);
void RegExpCodeCreateEvent(Logger::LogEventsAndTags tag,
......@@ -212,10 +213,10 @@ class CpuProfiler {
static void StartProfiling(const char* title);
static void StartProfiling(String* title);
static CpuProfile* StopProfiling(const char* title);
static CpuProfile* StopProfiling(String* title);
static CpuProfile* StopProfiling(Object* security_token, String* title);
static int GetProfilesCount();
static CpuProfile* GetProfile(int index);
static CpuProfile* FindProfile(unsigned uid);
static CpuProfile* GetProfile(Object* security_token, int index);
static CpuProfile* FindProfile(Object* security_token, unsigned uid);
// Invoked from stack sampler (thread or signal handler.)
static TickSample* TickSampleEvent();
......@@ -252,11 +253,12 @@ class CpuProfiler {
void StartCollectingProfile(String* title);
void StartProcessorIfNotStarted();
CpuProfile* StopCollectingProfile(const char* title);
CpuProfile* StopCollectingProfile(String* title);
CpuProfile* StopCollectingProfile(Object* security_token, String* title);
void StopProcessorIfLastProfile();
CpuProfilesCollection* profiles_;
unsigned next_profile_uid_;
TokenEnumerator* token_enumerator_;
ProfileGenerator* generator_;
ProfilerEventsProcessor* processor_;
int saved_logging_nesting_;
......
......@@ -170,7 +170,7 @@ void StackTracer::Trace(TickSample* sample) {
SafeStackTraceFrameIterator it(sample->fp, sample->sp,
sample->sp, js_entry_sp);
while (!it.done() && i < TickSample::kMaxFramesCount) {
sample->stack[i++] = it.frame()->pc();
sample->stack[i++] = reinterpret_cast<Address>(it.frame()->function());
it.Advance();
}
sample->frames_count = i;
......
......@@ -35,17 +35,30 @@
namespace v8 {
namespace internal {
CodeEntry::CodeEntry(int security_token_id)
: call_uid_(0),
tag_(Logger::FUNCTION_TAG),
name_prefix_(kEmptyNamePrefix),
name_(""),
resource_name_(""),
line_number_(0),
security_token_id_(security_token_id) {
}
CodeEntry::CodeEntry(Logger::LogEventsAndTags tag,
const char* name_prefix,
const char* name,
const char* resource_name,
int line_number)
int line_number,
int security_token_id)
: call_uid_(next_call_uid_++),
tag_(tag),
name_prefix_(name_prefix),
name_(name),
resource_name_(resource_name),
line_number_(line_number) {
line_number_(line_number),
security_token_id_(security_token_id) {
}
......
This diff is collapsed.
......@@ -35,14 +35,34 @@
namespace v8 {
namespace internal {
class TokenEnumerator {
public:
TokenEnumerator();
~TokenEnumerator();
int GetTokenId(Object* token);
private:
static void TokenRemovedCallback(v8::Persistent<v8::Value> handle,
void* parameter);
void TokenRemoved(Object** token_location);
List<Object**> token_locations_;
List<bool> token_removed_;
friend class TokenEnumeratorTester;
};
class CodeEntry {
public:
explicit INLINE(CodeEntry(int security_token_id));
// CodeEntry doesn't own name strings, just references them.
INLINE(CodeEntry(Logger::LogEventsAndTags tag,
const char* name_prefix,
const char* name,
const char* resource_name,
int line_number));
int line_number,
int security_token_id));
INLINE(bool is_js_function() const) { return is_js_function_tag(tag_); }
INLINE(const char* name_prefix() const) { return name_prefix_; }
......@@ -51,18 +71,24 @@ class CodeEntry {
INLINE(const char* resource_name() const) { return resource_name_; }
INLINE(int line_number() const) { return line_number_; }
INLINE(unsigned call_uid() const) { return call_uid_; }
INLINE(int security_token_id() const) { return security_token_id_; }
INLINE(static bool is_js_function_tag(Logger::LogEventsAndTags tag));
void CopyData(const CodeEntry& source);
static const char* kEmptyNamePrefix;
static const int kNoSecurityToken = -1;
static const int kInheritsSecurityToken = -2;
private:
const unsigned call_uid_;
unsigned call_uid_;
Logger::LogEventsAndTags tag_;
const char* name_prefix_;
const char* name_;
const char* resource_name_;
int line_number_;
int security_token_id_;
static unsigned next_call_uid_;
......@@ -79,6 +105,7 @@ class ProfileNode {
ProfileNode* FindChild(CodeEntry* entry);
ProfileNode* FindOrAddChild(CodeEntry* entry);
INLINE(void IncrementSelfTicks()) { ++self_ticks_; }
INLINE(void IncreaseSelfTicks(unsigned amount)) { self_ticks_ += amount; }
INLINE(void IncreaseTotalTicks(unsigned amount)) { total_ticks_ += amount; }
INLINE(CodeEntry* entry() const) { return entry_; }
......@@ -119,6 +146,7 @@ class ProfileTree {
void AddPathFromEnd(const Vector<CodeEntry*>& path);
void AddPathFromStart(const Vector<CodeEntry*>& path);
void CalculateTotalTicks();
void FilteredClone(ProfileTree* src, int security_token_id);
double TicksToMillis(unsigned ticks) const {
return ticks * ms_to_ticks_scale_;
......@@ -133,7 +161,7 @@ class ProfileTree {
private:
template <typename Callback>
void TraverseDepthFirstPostOrder(Callback* callback);
void TraverseDepthFirst(Callback* callback);
CodeEntry root_entry_;
ProfileNode* root_;
......@@ -152,6 +180,7 @@ class CpuProfile {
void AddPath(const Vector<CodeEntry*>& path);
void CalculateTotalTicks();
void SetActualSamplingRate(double actual_sampling_rate);
CpuProfile* FilteredClone(int security_token_id);
INLINE(const char* title() const) { return title_; }
INLINE(unsigned uid() const) { return uid_; }
......@@ -179,7 +208,7 @@ class CodeMap {
INLINE(void AddCode(Address addr, CodeEntry* entry, unsigned size));
INLINE(void MoveCode(Address from, Address to));
INLINE(void DeleteCode(Address addr));
void AddAlias(Address alias, Address addr);
void AddAlias(Address start, CodeEntry* entry, Address code_start);
CodeEntry* FindEntry(Address addr);
void Print();
......@@ -221,10 +250,14 @@ class CpuProfilesCollection {
bool StartProfiling(const char* title, unsigned uid);
bool StartProfiling(String* title, unsigned uid);
CpuProfile* StopProfiling(const char* title, double actual_sampling_rate);
CpuProfile* StopProfiling(String* title, double actual_sampling_rate);
INLINE(List<CpuProfile*>* profiles()) { return &profiles_; }
CpuProfile* GetProfile(unsigned uid);
CpuProfile* StopProfiling(int security_token_id,
const char* title,
double actual_sampling_rate);
CpuProfile* StopProfiling(int security_token_id,
String* title,
double actual_sampling_rate);
List<CpuProfile*>* Profiles(int security_token_id);
CpuProfile* GetProfile(int security_token_id, unsigned uid);
inline bool is_last_profile();
CodeEntry* NewCodeEntry(Logger::LogEventsAndTags tag,
......@@ -233,6 +266,7 @@ class CpuProfilesCollection {
CodeEntry* NewCodeEntry(Logger::LogEventsAndTags tag,
const char* name_prefix, String* name);
CodeEntry* NewCodeEntry(Logger::LogEventsAndTags tag, int args_count);
CodeEntry* NewCodeEntry(int security_token_id);
// Called from profile generator thread.
void AddPathToCurrentProfiles(const Vector<CodeEntry*>& path);
......@@ -242,13 +276,15 @@ class CpuProfilesCollection {
INLINE(const char* GetFunctionName(const char* name));
const char* GetName(String* name);
const char* GetName(int args_count);
List<CpuProfile*>* GetProfilesList(int security_token_id);
int TokenToIndex(int security_token_id);
INLINE(static bool StringsMatch(void* key1, void* key2)) {
return strcmp(reinterpret_cast<char*>(key1),
reinterpret_cast<char*>(key2)) == 0;
}
INLINE(static bool CpuProfilesMatch(void* key1, void* key2)) {
INLINE(static bool UidsMatch(void* key1, void* key2)) {
return key1 == key2;
}
......@@ -257,8 +293,8 @@ class CpuProfilesCollection {
// args_count -> char*
List<char*> args_count_names_;
List<CodeEntry*> code_entries_;
List<CpuProfile*> profiles_;
// uid -> CpuProfile*
List<List<CpuProfile*>* > profiles_by_token_;
// uid -> index
HashMap profiles_uids_;
// Accessed by VM thread and profile generator thread.
......@@ -332,6 +368,10 @@ class ProfileGenerator {
return profiles_->NewCodeEntry(tag, args_count);
}
INLINE(CodeEntry* NewCodeEntry(int security_token_id)) {
return profiles_->NewCodeEntry(security_token_id);
}
void RecordTickSample(const TickSample& sample);
INLINE(CodeMap* code_map()) { return &code_map_; }
......
......@@ -114,7 +114,8 @@ TEST(CodeEvents) {
processor.CodeMoveEvent(ToAddress(0x1400), ToAddress(0x1500));
processor.CodeCreateEvent(i::Logger::STUB_TAG, 3, ToAddress(0x1600), 0x10);
processor.CodeDeleteEvent(ToAddress(0x1600));
processor.FunctionCreateEvent(ToAddress(0x1700), ToAddress(0x1000));
processor.FunctionCreateEvent(ToAddress(0x1700), ToAddress(0x1000),
CodeEntry::kNoSecurityToken);
// Enqueue a tick event to enable code events processing.
EnqueueTickSampleEvent(&processor, ToAddress(0x1000));
......@@ -176,7 +177,8 @@ TEST(TickEvents) {
processor.Stop();
processor.Join();
CpuProfile* profile = profiles.StopProfiling("", 1);
CpuProfile* profile =
profiles.StopProfiling(CodeEntry::kNoSecurityToken, "", 1);
CHECK_NE(NULL, profile);
// Check call trees.
......
......@@ -66,28 +66,6 @@ static void DoTraceHideCEntryFPAddress(Address fp) {
}
static void CheckRetAddrIsInFunction(const char* func_name,
Address ret_addr,
Address func_start_addr,
unsigned int func_len) {
printf("CheckRetAddrIsInFunction \"%s\": %p %p %p\n",
func_name, func_start_addr, ret_addr, func_start_addr + func_len);
CHECK_GE(ret_addr, func_start_addr);
CHECK_GE(func_start_addr + func_len, ret_addr);
}
static void CheckRetAddrIsInJSFunction(const char* func_name,
Address ret_addr,
Handle<JSFunction> func) {
v8::internal::Code* func_code = func->code();
CheckRetAddrIsInFunction(
func_name, ret_addr,
func_code->instruction_start(),
func_code->ExecutableSize());
}
// --- T r a c e E x t e n s i o n ---
class TraceExtension : public v8::Extension {
......@@ -209,11 +187,16 @@ static Handle<JSFunction> GetGlobalJSFunction(const char* name) {
}
static void CheckRetAddrIsInJSFunction(const char* func_name,
Address ret_addr) {
CheckRetAddrIsInJSFunction(func_name,
ret_addr,
GetGlobalJSFunction(func_name));
static void CheckObjectIsJSFunction(const char* func_name,
Address addr) {
i::Object* obj = reinterpret_cast<i::Object*>(addr);
CHECK(obj->IsJSFunction());
CHECK(JSFunction::cast(obj)->shared()->name()->IsString());
i::SmartPointer<char> found_name =
i::String::cast(
JSFunction::cast(
obj)->shared()->name())->ToCString();
CHECK_EQ(func_name, *found_name);
}
......@@ -272,6 +255,7 @@ static void CreateTraceCallerFunction(const char* func_name,
Handle<JSFunction> func = CompileFunction(trace_call_buf.start());
CHECK(!func.is_null());
i::FLAG_allow_natives_syntax = allow_natives_syntax;
func->shared()->set_name(*NewString(func_name));
#ifdef DEBUG
v8::internal::Code* func_code = func->code();
......@@ -313,10 +297,8 @@ TEST(CFromJSStackTrace) {
// StackTracer::Trace
CHECK_GT(sample.frames_count, 1);
// Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
CheckRetAddrIsInJSFunction("JSFuncDoTrace",
sample.stack[0]);
CheckRetAddrIsInJSFunction("JSTrace",
sample.stack[1]);
CheckObjectIsJSFunction("JSFuncDoTrace", sample.stack[0]);
CheckObjectIsJSFunction("JSTrace", sample.stack[1]);
}
......@@ -359,10 +341,8 @@ TEST(PureJSStackTrace) {
sample.function);
CHECK_GT(sample.frames_count, 1);
// Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
CheckRetAddrIsInJSFunction("JSTrace",
sample.stack[0]);
CheckRetAddrIsInJSFunction("OuterJSTrace",
sample.stack[1]);
CheckObjectIsJSFunction("JSTrace", sample.stack[0]);
CheckObjectIsJSFunction("OuterJSTrace", sample.stack[1]);
}
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment