ExecutionTracer.h (25569B)
1 /* -*- Mode: C++; tab-width: 8; 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 #ifndef debugger_ExecutionTracer_h 8 #define debugger_ExecutionTracer_h 9 10 #include "mozilla/Assertions.h" // MOZ_DIAGNOSTIC_ASSERT, MOZ_ASSERT 11 #include "mozilla/BaseProfilerUtils.h" // profiler_current_thread_id 12 #include "mozilla/EndianUtils.h" // NativeEndian 13 #include "mozilla/MathAlgorithms.h" // mozilla::IsPowerOfTwo 14 #include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some, mozilla::Nothing 15 #include "mozilla/Span.h" 16 #include "mozilla/TimeStamp.h" 17 18 #include <limits> // std::numeric_limits 19 #include <stddef.h> // size_t 20 #include <stdint.h> // uint8_t, uint16_t 21 22 #include "js/CharacterEncoding.h" // JS::UTF8Chars 23 #include "js/Debug.h" // JS::ExecutionTrace 24 #include "js/RootingAPI.h" // JS::Rooted 25 #include "js/Utility.h" // js_malloc, js_free 26 #include "js/Value.h" // JS::Value 27 #include "vm/ErrorObject.h" // ErrorObject 28 #include "vm/JSContext.h" // JSContext 29 #include "vm/Stack.h" // js::AbstractFramePtr 30 31 namespace js { 32 33 enum class OutOfLineEntryType : uint8_t { 34 ScriptURL, 35 Atom, 36 Shape, 37 }; 38 39 enum class InlineEntryType : uint8_t { 40 StackFunctionEnter, 41 StackFunctionLeave, 42 LabelEnter, 43 LabelLeave, 44 Error, 45 }; 46 47 enum class PropertyKeyKind : uint8_t { 48 Undefined, 49 String, 50 Int, 51 Symbol, 52 }; 53 54 using TracingScratchBuffer = mozilla::Vector<char, 512>; 55 56 // TODO: it should be noted that part of this design is informed by the fact 57 // that it evolved from a prototype which wrote this data from a content 58 // process and read it from the parent process, allowing the parent process to 59 // display the trace in real time as the program executes. Bug 1910182 tracks 60 // the next steps for making that prototype a reality. 61 template <size_t BUFFER_SIZE> 62 class TracingBuffer { 63 static_assert(mozilla::IsPowerOfTwo(BUFFER_SIZE)); 64 65 // BUFFER_SIZE is the size of the underlying ring buffer, and BUFFER_MASK 66 // masks off indices into it in order to wrap around 67 static const size_t BUFFER_MASK = BUFFER_SIZE - 1; 68 69 // The entry header is just a u16 that holds the size of the entry in bytes. 70 // This is used for asserting the integrity of the data as well as for 71 // skipping the read head forward if it's going to be overwritten by the 72 // write head 73 static const size_t ENTRY_HEADER_SIZE = sizeof(uint16_t); 74 75 // The underlying ring buffer 76 uint8_t* buffer_ = nullptr; 77 78 // NOTE: The following u64s are unwrapped indices into the ring buffer, so 79 // they must always be masked off with BUFFER_MASK before using them to 80 // access buffer_: 81 82 // Represents how much has been written into the ring buffer and is ready 83 // for reading 84 uint64_t writeHead_ = 0; 85 86 // Represents how much has been read from the ring buffer 87 uint64_t readHead_ = 0; 88 89 // When not equal to writeHead_, this represents unfinished write progress 90 // into the buffer. After each entry successfully finished writing, 91 // writeHead_ is set to this value 92 uint64_t uncommittedWriteHead_ = 0; 93 94 // Similar to uncommittedWriteHead_, but for the purposes of reading 95 uint64_t uncommittedReadHead_ = 0; 96 97 bool ensureScratchBufferSize(TracingScratchBuffer& scratchBuffer, 98 size_t requiredSize) { 99 if (scratchBuffer.length() >= requiredSize) { 100 return true; 101 } 102 return scratchBuffer.growByUninitialized(requiredSize - 103 scratchBuffer.length()); 104 } 105 106 public: 107 static const size_t SIZE = BUFFER_SIZE; 108 109 ~TracingBuffer() { 110 if (buffer_) { 111 js_free(buffer_); 112 } 113 } 114 115 bool init() { 116 buffer_ = static_cast<uint8_t*>(js_malloc(BUFFER_SIZE)); 117 return buffer_ != nullptr; 118 } 119 120 bool readable() { return writeHead_ > readHead_; } 121 122 uint64_t uncommittedWriteHead() { return uncommittedWriteHead_; } 123 124 uint64_t readHead() { return readHead_; } 125 126 void beginWritingEntry() { 127 // uncommittedWriteHead_ can be > writeHead_ if a previous write failed. 128 // In that case, this effectively discards whatever was written during that 129 // time 130 MOZ_ASSERT(uncommittedWriteHead_ >= writeHead_); 131 uncommittedWriteHead_ = writeHead_; 132 uncommittedWriteHead_ += ENTRY_HEADER_SIZE; 133 } 134 135 void finishWritingEntry() { 136 MOZ_RELEASE_ASSERT(uncommittedWriteHead_ - writeHead_ <= 137 std::numeric_limits<uint16_t>::max()); 138 uint16_t entryHeader = uint16_t(uncommittedWriteHead_ - writeHead_); 139 writeBytesAtOffset(reinterpret_cast<const uint8_t*>(&entryHeader), 140 sizeof(entryHeader), writeHead_); 141 writeHead_ = uncommittedWriteHead_; 142 } 143 144 void beginReadingEntry() { 145 MOZ_ASSERT(uncommittedReadHead_ == readHead_); 146 // We will read the entry header (still pointed to by readHead_) from 147 // inside finishReadingEntry 148 uncommittedReadHead_ += ENTRY_HEADER_SIZE; 149 } 150 151 void finishReadingEntry() { 152 uint16_t entryHeader; 153 readBytesAtOffset(reinterpret_cast<uint8_t*>(&entryHeader), 154 sizeof(entryHeader), readHead_); 155 size_t read = uncommittedReadHead_ - readHead_; 156 157 MOZ_RELEASE_ASSERT(entryHeader == uint16_t(read)); 158 readHead_ += entryHeader; 159 uncommittedReadHead_ = readHead_; 160 } 161 162 void skipEntry() { 163 uint16_t entryHeader; 164 readBytesAtOffset(reinterpret_cast<uint8_t*>(&entryHeader), 165 sizeof(entryHeader), readHead_); 166 readHead_ += entryHeader; 167 uncommittedReadHead_ = readHead_; 168 } 169 170 void writeBytesAtOffset(const uint8_t* bytes, size_t length, 171 uint64_t offset) { 172 MOZ_ASSERT(offset + length <= readHead_ + BUFFER_SIZE); 173 174 size_t maskedWriteHead = offset & BUFFER_MASK; 175 if (maskedWriteHead + length > BUFFER_SIZE) { 176 size_t firstChunk = BUFFER_SIZE - maskedWriteHead; 177 memcpy(buffer_ + maskedWriteHead, bytes, firstChunk); 178 memcpy(buffer_, bytes + firstChunk, length - firstChunk); 179 } else { 180 memcpy(buffer_ + maskedWriteHead, bytes, length); 181 } 182 } 183 184 void writeBytes(const uint8_t* bytes, size_t length) { 185 // Skip the read head forward if we're about to overwrite unread entries 186 while (MOZ_UNLIKELY(uncommittedWriteHead_ + length > 187 readHead_ + BUFFER_SIZE)) { 188 skipEntry(); 189 } 190 191 writeBytesAtOffset(bytes, length, uncommittedWriteHead_); 192 uncommittedWriteHead_ += length; 193 } 194 195 template <typename T> 196 void write(T val) { 197 // No magic hidden work allowed here - we are just reducing duplicate code 198 // serializing integers and floats. 199 static_assert(std::is_arithmetic_v<T>); 200 if constexpr (sizeof(T) > 1) { 201 val = mozilla::NativeEndian::swapToLittleEndian(val); 202 } 203 204 writeBytes(reinterpret_cast<const uint8_t*>(&val), sizeof(T)); 205 } 206 207 template <typename T> 208 void writeAtOffset(T val, uint64_t offset) { 209 static_assert(std::is_arithmetic_v<T>); 210 if constexpr (sizeof(T) > 1) { 211 val = mozilla::NativeEndian::swapToLittleEndian(val); 212 } 213 214 writeBytesAtOffset(reinterpret_cast<const uint8_t*>(&val), sizeof(T), 215 offset); 216 } 217 218 void writeEmptyString() { 219 write(uint8_t(JS::TracerStringEncoding::Latin1)); 220 write(uint32_t(0)); // length 221 } 222 223 void writeEmptySmallString() { write(uint16_t(0)); } 224 225 enum class InlineStringEncoding { No, Yes }; 226 227 // Helper for writing the length and encoding, which are sometimes squished 228 // into one value 229 template <typename LengthType = uint32_t, 230 InlineStringEncoding InlineEncoding = InlineStringEncoding::No> 231 void writeAdjustedLengthAndEncoding( 232 size_t* length, JS::TracerStringEncoding encoding, 233 size_t lengthLimit = std::numeric_limits<LengthType>::max()) { 234 if (*length > lengthLimit) { 235 *length = lengthLimit; 236 } 237 238 if constexpr (InlineEncoding == InlineStringEncoding::No) { 239 write(uint8_t(encoding)); 240 write(LengthType(*length)); 241 } else { 242 constexpr LengthType encodingBits = 2; 243 LengthType typedLength = 244 LengthType(*length) | 245 (uint16_t(encoding) << (sizeof(LengthType) * 8 - encodingBits)); 246 write(typedLength); 247 } 248 } 249 250 template <typename LengthType = uint32_t, 251 InlineStringEncoding InlineEncoding = InlineStringEncoding::No> 252 bool writeString( 253 JSContext* cx, JS::Handle<JSString*> str, 254 size_t lengthLimit = std::numeric_limits<LengthType>::max()) { 255 JS::TracerStringEncoding encoding; 256 if (str->hasLatin1Chars()) { 257 encoding = JS::TracerStringEncoding::Latin1; 258 } else { 259 encoding = JS::TracerStringEncoding::TwoByte; 260 } 261 262 // TODO: if ropes are common we can certainly serialize them without 263 // linearizing - this is just easy 264 JSLinearString* linear = str->ensureLinear(cx); 265 if (!linear) { 266 return false; 267 } 268 269 size_t length = linear->length(); 270 writeAdjustedLengthAndEncoding<LengthType, InlineEncoding>( 271 &length, encoding, lengthLimit); 272 273 size_t size = length; 274 JS::AutoAssertNoGC nogc; 275 const uint8_t* charBuffer = nullptr; 276 if (encoding == JS::TracerStringEncoding::TwoByte) { 277 size *= sizeof(char16_t); 278 charBuffer = reinterpret_cast<const uint8_t*>(linear->twoByteChars(nogc)); 279 } else { 280 charBuffer = reinterpret_cast<const uint8_t*>(linear->latin1Chars(nogc)); 281 } 282 writeBytes(charBuffer, size); 283 return true; 284 } 285 286 template <typename CharType, JS::TracerStringEncoding Encoding, 287 typename LengthType = uint32_t, 288 InlineStringEncoding InlineEncoding = InlineStringEncoding::No> 289 void writeCString( 290 const CharType* chars, 291 size_t lengthLimit = std::numeric_limits<LengthType>::max()) { 292 size_t length = std::char_traits<CharType>::length(chars); 293 static_assert(sizeof(CharType) == 1 || 294 Encoding == JS::TracerStringEncoding::TwoByte); 295 static_assert(sizeof(CharType) <= 2); 296 297 writeAdjustedLengthAndEncoding<LengthType, InlineEncoding>( 298 &length, Encoding, lengthLimit); 299 300 const size_t size = length * sizeof(CharType); 301 writeBytes(reinterpret_cast<const uint8_t*>(chars), size); 302 } 303 304 bool writeSmallString(JSContext* cx, JS::Handle<JSString*> str) { 305 return writeString<uint16_t, InlineStringEncoding::Yes>( 306 cx, str, JS::ValueSummary::SMALL_STRING_LENGTH_LIMIT); 307 } 308 309 template <typename CharType, JS::TracerStringEncoding Encoding> 310 void writeSmallCString(const CharType* chars) { 311 writeCString<CharType, Encoding, char16_t, InlineStringEncoding::Yes>( 312 chars, JS::ValueSummary::SMALL_STRING_LENGTH_LIMIT); 313 } 314 315 void readBytesAtOffset(uint8_t* bytes, size_t length, uint64_t offset) { 316 size_t maskedReadHead = offset & BUFFER_MASK; 317 if (maskedReadHead + length > BUFFER_SIZE) { 318 size_t firstChunk = BUFFER_SIZE - maskedReadHead; 319 memcpy(bytes, buffer_ + maskedReadHead, firstChunk); 320 memcpy(bytes + firstChunk, buffer_, length - firstChunk); 321 } else { 322 memcpy(bytes, buffer_ + maskedReadHead, length); 323 } 324 } 325 326 void readBytes(uint8_t* bytes, size_t length) { 327 readBytesAtOffset(bytes, length, uncommittedReadHead_); 328 uncommittedReadHead_ += length; 329 } 330 331 template <typename T> 332 void read(T* val) { 333 static_assert(std::is_arithmetic_v<T>); 334 335 readBytes(reinterpret_cast<uint8_t*>(val), sizeof(T)); 336 if constexpr (sizeof(T) > 1) { 337 *val = mozilla::NativeEndian::swapFromLittleEndian(*val); 338 } 339 } 340 341 // Reads a string from our buffer into the stringBuffer. Converts everything 342 // to null-terminated UTF-8 343 template <typename LengthType = uint32_t, 344 InlineStringEncoding InlineEncoding = InlineStringEncoding::No> 345 bool readString(TracingScratchBuffer& scratchBuffer, 346 mozilla::Vector<char>& stringBuffer, size_t* index) { 347 uint8_t encodingByte; 348 LengthType length; 349 if constexpr (InlineEncoding == InlineStringEncoding::Yes) { 350 LengthType lengthAndEncoding; 351 read(&lengthAndEncoding); 352 constexpr LengthType encodingBits = 2; 353 constexpr LengthType encodingShift = 354 sizeof(LengthType) * 8 - encodingBits; 355 constexpr LengthType encodingMask = 0b11 << encodingShift; 356 length = lengthAndEncoding & ~encodingMask; 357 encodingByte = (lengthAndEncoding & encodingMask) >> encodingShift; 358 } else { 359 read(&encodingByte); 360 read(&length); 361 } 362 363 JS::TracerStringEncoding encoding = JS::TracerStringEncoding(encodingByte); 364 365 *index = stringBuffer.length(); 366 367 if (length == 0) { 368 if (!stringBuffer.append('\0')) { 369 return false; 370 } 371 return true; 372 } 373 374 if (encoding == JS::TracerStringEncoding::UTF8) { 375 size_t reserveLength = length + 1; 376 if (!stringBuffer.growByUninitialized(reserveLength)) { 377 return false; 378 } 379 char* writePtr = stringBuffer.end() - reserveLength; 380 readBytes(reinterpret_cast<uint8_t*>(writePtr), length); 381 writePtr[length] = '\0'; 382 } else if (encoding == JS::TracerStringEncoding::Latin1) { 383 if (!ensureScratchBufferSize(scratchBuffer, length)) { 384 return false; 385 } 386 readBytes(reinterpret_cast<uint8_t*>(scratchBuffer.begin()), length); 387 388 // A single latin-1 code point maps to either 1 or 2 UTF-8 code units. 389 // The + 1 is for the null terminator. 390 size_t reserveLength = length * 2 + 1; 391 if (!stringBuffer.reserve(stringBuffer.length() + reserveLength)) { 392 return false; 393 } 394 char* writePtr = stringBuffer.end(); 395 396 size_t convertedLength = mozilla::ConvertLatin1toUtf8( 397 mozilla::Span<const char>(scratchBuffer.begin(), length), 398 mozilla::Span<char>(writePtr, reserveLength)); 399 writePtr[convertedLength] = 0; 400 401 // We reserved above, which just grows the capacity but not the length. 402 // This just commits the exact length increase. 403 if (!stringBuffer.growByUninitialized(convertedLength + 1)) { 404 return false; 405 } 406 } else { 407 MOZ_ASSERT(encoding == JS::TracerStringEncoding::TwoByte); 408 if (!ensureScratchBufferSize(scratchBuffer, length * sizeof(char16_t))) { 409 return false; 410 } 411 readBytes(reinterpret_cast<uint8_t*>(scratchBuffer.begin()), 412 length * sizeof(char16_t)); 413 414 // Non-surrogate-paired single UTF-16 code unit maps to 1 to 3 UTF-8 415 // code units. Surrogate paired UTF-16 code units map to 4 to 6 UTF-8 416 // code units. 417 size_t reserveLength = length * 3 + 1; 418 if (!stringBuffer.reserve(stringBuffer.length() + reserveLength)) { 419 return false; 420 } 421 char* writePtr = stringBuffer.end(); 422 423 size_t convertedLength = mozilla::ConvertUtf16toUtf8( 424 mozilla::Span<const char16_t>( 425 reinterpret_cast<char16_t*>(scratchBuffer.begin()), length), 426 mozilla::Span<char>(writePtr, reserveLength)); 427 writePtr[convertedLength] = 0; 428 429 // We reserved above, which just grows the capacity but not the length. 430 // This just commits the exact length increase. 431 if (!stringBuffer.growByUninitialized(convertedLength + 1)) { 432 return false; 433 } 434 } 435 436 return true; 437 } 438 439 bool readSmallString(TracingScratchBuffer& scratchBuffer, 440 mozilla::Vector<char>& stringBuffer, size_t* index) { 441 return readString<uint16_t, InlineStringEncoding::Yes>(scratchBuffer, 442 stringBuffer, index); 443 } 444 }; 445 446 // These sizes are to some degree picked out of a hat, and eventually it might 447 // be nice to make them configurable. For reference, I measured it costing 448 // 145MB to open gdocs and create an empty document, so 256MB is just some 449 // extra wiggle room for complex use cases. 450 using InlineDataBuffer = TracingBuffer<1 << 28>; 451 452 // We include a separate buffer for value summaries, so that we can store them 453 // contiguously and so we don't lose information from the inline data if a 454 // script has a lot of large values for instance. 455 using ValueDataBuffer = InlineDataBuffer; 456 457 // The size for the out of line data is much smaller, so I just picked a size 458 // that was much smaller but big enough that I didn't see us running out of it 459 // when playing around on various complex apps. Again, it would be great in the 460 // future for this to be configurable. 461 using OutOfLineDataBuffer = TracingBuffer<1 << 22>; 462 463 class ValueSummaries { 464 ValueDataBuffer* valueData_ = nullptr; 465 OutOfLineDataBuffer* outOfLineData_ = nullptr; 466 467 friend struct ::JS_TracerSummaryWriter; 468 469 public: 470 // Sometimes we write ValueSummarys as nested properties of other 471 // ValueSummarys. This enum is used to indicate that in code when necessary. 472 // (This value is not written into the serialized format, and should instead 473 // be tracked by the reader) 474 enum class IsNested { No, Yes }; 475 476 void init(ValueDataBuffer* valueData, OutOfLineDataBuffer* outOfLineData) { 477 valueData_ = valueData; 478 outOfLineData_ = outOfLineData; 479 } 480 481 bool writeValue(JSContext* cx, JS::Handle<JS::Value> val, IsNested nested); 482 483 // valueBufferIndex will hold the index at which we wrote the arguments into 484 // the valueData_ buffer 485 bool writeArguments(JSContext* cx, AbstractFramePtr frame, 486 uint64_t* valueBufferIndex); 487 488 // Unrolls the underlying ring buffer into a contiguous, compacted buffer 489 // and puts it into the context's valueBuffer field. 490 bool populateOutputBuffer(JS::ExecutionTrace::TracedJSContext& context); 491 492 // If ringBufferIndex is still valid, translates it into an index into the 493 // output buffer. Otherwise, this returns 494 // JS::ExecutionTrace::EXPIRED_VALUES_MAGIC. 495 int32_t getOutputBufferIndex(uint64_t ringBufferIndex); 496 497 void writeHeader(JS::ValueType type, uint8_t flags); 498 bool writeShapeSummary(JSContext* cx, JS::Handle<NativeShape*> shape); 499 500 // Only writes the class name 501 bool writeMinimalShapeSummary(JSContext* cx, JS::Handle<Shape*> shape); 502 503 void writeObjectHeader(JS::ObjectSummary::Kind kind, uint8_t flags); 504 505 bool writeObject(JSContext* cx, JS::Handle<JSObject*> obj, IsNested nested); 506 507 bool writeFunctionSummary(JSContext* cx, JS::Handle<JSFunction*> fn, 508 IsNested nested); 509 bool writeArrayObjectSummary(JSContext* cx, JS::Handle<ArrayObject*> array, 510 IsNested nested); 511 bool writeSetObjectSummary(JSContext* cx, JS::Handle<SetObject*> set, 512 IsNested nested); 513 bool writeMapObjectSummary(JSContext* cx, JS::Handle<MapObject*> map, 514 IsNested nested); 515 bool writeErrorObjectSummary(JSContext* cx, JS::Handle<JSObject*> obj, 516 JS::Handle<ErrorObject*> error, IsNested nested); 517 bool writeGenericOrWrappedPrimitiveObjectSummary( 518 JSContext* cx, JS::Handle<NativeObject*> nobj, IsNested nested); 519 bool writeExternalObjectSummary(JSContext* cx, JS::Handle<NativeObject*> nobj, 520 IsNested nested); 521 522 bool writeStringLikeValue(JSContext* cx, JS::ValueType valueType, 523 JS::Handle<JSString*> str); 524 }; 525 526 // An ExecutionTracer is responsible for recording JS execution while it is 527 // enabled to a set of ring buffers, and providing that information as a JS 528 // object when requested. See Debugger.md (collectNativeTrace) for more details. 529 class ExecutionTracer { 530 private: 531 // The fields below should only be accessed while we hold the lock. 532 static Mutex globalInstanceLock MOZ_UNANNOTATED; 533 static mozilla::Vector<ExecutionTracer*> globalInstances; 534 535 // The buffers below should only be accessed while we hold the lock. 536 Mutex bufferLock_ MOZ_UNANNOTATED; 537 538 // This holds the actual entries, one for each push or pop of a frame or label 539 InlineDataBuffer inlineData_; 540 541 // This holds data that may be duplicated across entries, like script URLs or 542 // function names. This should generally be much smaller in terms of raw 543 // bytes. Note however that we can still wrap around this buffer and lose 544 // entries - the system is best effort, and the consumer must accomodate the 545 // fact that entries from inlineData_ may reference expired data from 546 // outOfLineData_ 547 OutOfLineDataBuffer outOfLineData_; 548 549 // This holds summaries of various values recorded during tracing. Currently 550 // this only contains values for function arguments. TODO: Add support for 551 // function return values. 552 ValueDataBuffer valueData_; 553 554 // This is just an ID that allows the profiler to easily correlate the trace 555 // for a given context with the correct thread in the output profile. 556 // We're operating on the assumption that there is one JSContext per thread, 557 // which should be true enough for our uses in Firefox, but doesn't have to 558 // be true everywhere. 559 mozilla::baseprofiler::BaseProfilerThreadId threadId_; 560 561 // This is a helper class for writing value data to the valueData_ and 562 // outOfLineData_ buffers. It holds pointers to those two buffers and houses 563 // all of the logic for writing the value summaries themselves. 564 ValueSummaries valueSummaries_; 565 566 // When we encounter an error during tracing, we write one final Error entry 567 // and suspend tracing indefinitely. This allows the consumer to get some 568 // information about what led up to the error, while preventing any 569 // additional future overhead. An alternative to this approach would be to 570 // clean up all of our buffers on error, but since the user must have elected 571 // to turn on tracing, we assume that they would rather have a greater chance 572 // of more information about what led up to the error rather than a greater 573 // chance of avoiding a crash due to OOM. 574 void handleError(JSContext* cx); 575 576 void writeScriptUrl(ScriptSource* scriptSource); 577 578 // Writes an atom into the outOfLineData_, associating it with the specified 579 // id. In practice, `id` comes from an atom id inside a cache in the 580 // JSContext which is incremented each time a new atom is registered and 581 // cleared when tracing is done. 582 bool writeAtom(JSContext* cx, JS::Handle<JSAtom*> atom, uint32_t id); 583 bool writeFunctionFrame(JSContext* cx, AbstractFramePtr frame); 584 585 // The below functions read data from the inlineData_ and outOfLineData_ ring 586 // buffers into structs to be consumed by clients of the 587 // JS_TracerSnapshotTrace API. 588 bool readFunctionFrame(JS::ExecutionTrace::EventKind kind, 589 JS::ExecutionTrace::TracedEvent& event); 590 bool readLabel(JS::ExecutionTrace::EventKind kind, 591 JS::ExecutionTrace::TracedEvent& event, 592 TracingScratchBuffer& scratchBuffer, 593 mozilla::Vector<char>& stringBuffer); 594 bool readInlineEntry(mozilla::Vector<JS::ExecutionTrace::TracedEvent>& events, 595 TracingScratchBuffer& scratchBuffer, 596 mozilla::Vector<char>& stringBuffer); 597 bool readOutOfLineEntry( 598 mozilla::HashMap<uint32_t, size_t>& scriptUrls, 599 mozilla::HashMap<uint32_t, size_t>& atoms, 600 mozilla::Vector<JS::ExecutionTrace::ShapeSummary>& shapes, 601 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer); 602 bool readInlineEntries( 603 mozilla::Vector<JS::ExecutionTrace::TracedEvent>& events, 604 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer); 605 bool readOutOfLineEntries( 606 mozilla::HashMap<uint32_t, size_t>& scriptUrls, 607 mozilla::HashMap<uint32_t, size_t>& atoms, 608 mozilla::Vector<JS::ExecutionTrace::ShapeSummary>& shapes, 609 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer); 610 611 public: 612 ExecutionTracer() : bufferLock_(mutexid::ExecutionTracerInstanceLock) {} 613 614 ~ExecutionTracer() { 615 LockGuard<Mutex> guard(globalInstanceLock); 616 617 globalInstances.eraseIfEqual(this); 618 } 619 620 mozilla::baseprofiler::BaseProfilerThreadId threadId() const { 621 return threadId_; 622 } 623 624 bool init() { 625 LockGuard<Mutex> guard(globalInstanceLock); 626 LockGuard<Mutex> guard2(bufferLock_); 627 628 threadId_ = mozilla::baseprofiler::profiler_current_thread_id(); 629 630 if (!inlineData_.init()) { 631 return false; 632 } 633 if (!outOfLineData_.init()) { 634 return false; 635 } 636 if (!valueData_.init()) { 637 return false; 638 } 639 640 if (!globalInstances.append(this)) { 641 return false; 642 } 643 644 valueSummaries_.init(&valueData_, &outOfLineData_); 645 646 return true; 647 } 648 649 void onEnterFrame(JSContext* cx, AbstractFramePtr frame); 650 void onLeaveFrame(JSContext* cx, AbstractFramePtr frame); 651 652 template <typename CharType, JS::TracerStringEncoding Encoding> 653 void onEnterLabel(const CharType* eventType); 654 template <typename CharType, JS::TracerStringEncoding Encoding> 655 void onLeaveLabel(const CharType* eventType); 656 657 // Reads the execution trace from the underlying ring buffers and outputs it 658 // into a native struct. For more information about this struct, see 659 // js/public/Debug.h 660 bool getNativeTrace(JS::ExecutionTrace::TracedJSContext& context, 661 TracingScratchBuffer& scratchBuffer, 662 mozilla::Vector<char>& stringBuffer); 663 664 // Calls getNativeTrace for every JSContext in the process, populating the 665 // provided ExecutionTrace with the result. 666 static bool getNativeTraceForAllContexts(JS::ExecutionTrace& trace); 667 }; 668 669 } // namespace js 670 671 #endif /* debugger_ExecutionTracer_h */