1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "ProfileBuffer.h"
8 
9 #include "mozilla/MathAlgorithms.h"
10 
11 #include "ProfilerMarker.h"
12 #include "jsfriendapi.h"
13 #include "nsScriptSecurityManager.h"
14 #include "nsJSPrincipals.h"
15 
16 using namespace mozilla;
17 
ProfileBuffer(uint32_t aEntrySize)18 ProfileBuffer::ProfileBuffer(uint32_t aEntrySize)
19     : mEntryIndexMask(0), mRangeStart(0), mRangeEnd(0), mEntrySize(0) {
20   // Round aEntrySize up to the nearest power of two, so that we can index
21   // mEntries with a simple mask and don't need to do a slow modulo operation.
22   const uint32_t UINT32_MAX_POWER_OF_TWO = 1 << 31;
23   MOZ_RELEASE_ASSERT(aEntrySize <= UINT32_MAX_POWER_OF_TWO,
24                      "aEntrySize is larger than what we support");
25   mEntrySize = RoundUpPow2(aEntrySize);
26   mEntryIndexMask = mEntrySize - 1;
27   mEntries = MakeUnique<ProfileBufferEntry[]>(mEntrySize);
28 }
29 
~ProfileBuffer()30 ProfileBuffer::~ProfileBuffer() {
31   while (mStoredMarkers.peek()) {
32     delete mStoredMarkers.popHead();
33   }
34 }
35 
36 // Called from signal, call only reentrant functions
AddEntry(const ProfileBufferEntry & aEntry)37 void ProfileBuffer::AddEntry(const ProfileBufferEntry& aEntry) {
38   GetEntry(mRangeEnd++) = aEntry;
39 
40   // The distance between mRangeStart and mRangeEnd must never exceed
41   // mEntrySize, so advance mRangeStart if necessary.
42   if (mRangeEnd - mRangeStart > mEntrySize) {
43     mRangeStart++;
44   }
45 }
46 
AddThreadIdEntry(int aThreadId)47 uint64_t ProfileBuffer::AddThreadIdEntry(int aThreadId) {
48   uint64_t pos = mRangeEnd;
49   AddEntry(ProfileBufferEntry::ThreadId(aThreadId));
50   return pos;
51 }
52 
AddStoredMarker(ProfilerMarker * aStoredMarker)53 void ProfileBuffer::AddStoredMarker(ProfilerMarker* aStoredMarker) {
54   aStoredMarker->SetPositionInBuffer(mRangeEnd);
55   mStoredMarkers.insert(aStoredMarker);
56 }
57 
CollectCodeLocation(const char * aLabel,const char * aStr,int aLineNumber,const Maybe<js::ProfileEntry::Category> & aCategory)58 void ProfileBuffer::CollectCodeLocation(
59     const char* aLabel, const char* aStr, int aLineNumber,
60     const Maybe<js::ProfileEntry::Category>& aCategory) {
61   AddEntry(ProfileBufferEntry::Label(aLabel));
62 
63   if (aStr) {
64     // Store the string using one or more DynamicStringFragment entries.
65     size_t strLen = strlen(aStr) + 1;  // +1 for the null terminator
66     for (size_t j = 0; j < strLen;) {
67       // Store up to kNumChars characters in the entry.
68       char chars[ProfileBufferEntry::kNumChars];
69       size_t len = ProfileBufferEntry::kNumChars;
70       if (j + len >= strLen) {
71         len = strLen - j;
72       }
73       memcpy(chars, &aStr[j], len);
74       j += ProfileBufferEntry::kNumChars;
75 
76       AddEntry(ProfileBufferEntry::DynamicStringFragment(chars));
77     }
78   }
79 
80   if (aLineNumber != -1) {
81     AddEntry(ProfileBufferEntry::LineNumber(aLineNumber));
82   }
83 
84   if (aCategory.isSome()) {
85     AddEntry(ProfileBufferEntry::Category(int(*aCategory)));
86   }
87 }
88 
DeleteExpiredStoredMarkers()89 void ProfileBuffer::DeleteExpiredStoredMarkers() {
90   // Delete markers of samples that have been overwritten due to circular
91   // buffer wraparound.
92   while (mStoredMarkers.peek() &&
93          mStoredMarkers.peek()->HasExpired(mRangeStart)) {
94     delete mStoredMarkers.popHead();
95   }
96 }
97 
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const98 size_t ProfileBuffer::SizeOfIncludingThis(
99     mozilla::MallocSizeOf aMallocSizeOf) const {
100   size_t n = aMallocSizeOf(this);
101   n += aMallocSizeOf(mEntries.get());
102 
103   // Measurement of the following members may be added later if DMD finds it
104   // is worthwhile:
105   // - memory pointed to by the elements within mEntries
106   // - mStoredMarkers
107 
108   return n;
109 }
110 
111 /* ProfileBufferCollector */
112 
IsChromeJSScript(JSScript * aScript)113 static bool IsChromeJSScript(JSScript* aScript) {
114   // WARNING: this function runs within the profiler's "critical section".
115   auto compartment = js::GetScriptCompartment(aScript);
116   return js::IsSystemCompartment(compartment);
117 }
118 
CollectNativeLeafAddr(void * aAddr)119 void ProfileBufferCollector::CollectNativeLeafAddr(void* aAddr) {
120   mBuf.AddEntry(ProfileBufferEntry::NativeLeafAddr(aAddr));
121 }
122 
CollectJitReturnAddr(void * aAddr)123 void ProfileBufferCollector::CollectJitReturnAddr(void* aAddr) {
124   mBuf.AddEntry(ProfileBufferEntry::JitReturnAddr(aAddr));
125 }
126 
CollectWasmFrame(const char * aLabel)127 void ProfileBufferCollector::CollectWasmFrame(const char* aLabel) {
128   mBuf.CollectCodeLocation("", aLabel, -1, Nothing());
129 }
130 
CollectPseudoEntry(const js::ProfileEntry & aEntry)131 void ProfileBufferCollector::CollectPseudoEntry(
132     const js::ProfileEntry& aEntry) {
133   // WARNING: this function runs within the profiler's "critical section".
134 
135   MOZ_ASSERT(aEntry.kind() == js::ProfileEntry::Kind::CPP_NORMAL ||
136              aEntry.kind() == js::ProfileEntry::Kind::JS_NORMAL);
137 
138   const char* label = aEntry.label();
139   const char* dynamicString = aEntry.dynamicString();
140   bool isChromeJSEntry = false;
141   int lineno = -1;
142 
143   if (aEntry.isJs()) {
144     // There are two kinds of JS frames that get pushed onto the PseudoStack.
145     //
146     // - label = "", dynamic string = <something>
147     // - label = "js::RunScript", dynamic string = nullptr
148     //
149     // The line number is only interesting in the first case.
150 
151     if (label[0] == '\0') {
152       MOZ_ASSERT(dynamicString);
153 
154       // We call aEntry.script() repeatedly -- rather than storing the result in
155       // a local variable in order -- to avoid rooting hazards.
156       if (aEntry.script()) {
157         isChromeJSEntry = IsChromeJSScript(aEntry.script());
158         if (aEntry.pc()) {
159           lineno = JS_PCToLineNumber(aEntry.script(), aEntry.pc());
160         }
161       }
162 
163     } else {
164       MOZ_ASSERT(strcmp(label, "js::RunScript") == 0 && !dynamicString);
165     }
166   } else {
167     MOZ_ASSERT(aEntry.isCpp());
168     lineno = aEntry.line();
169   }
170 
171   if (dynamicString) {
172     // Adjust the dynamic string as necessary.
173     if (ProfilerFeature::HasPrivacy(mFeatures) && !isChromeJSEntry) {
174       dynamicString = "(private)";
175     } else if (strlen(dynamicString) >= ProfileBuffer::kMaxFrameKeyLength) {
176       dynamicString = "(too long)";
177     }
178   }
179 
180   mBuf.CollectCodeLocation(label, dynamicString, lineno,
181                            Some(aEntry.category()));
182 }
183