Bug 1470795 Part 2 - Record/replay API changes for replay debugger, r=froydnj.

--HG--
extra : rebase_source : 798587fb1675a3a02a0ef3b7606577c5b63cc216
This commit is contained in:
Brian Hackett 2018-07-22 11:56:47 +00:00
parent 7c29c70818
commit 312bb81dda
6 changed files with 243 additions and 194 deletions

View File

@ -33,11 +33,11 @@ namespace recordreplay {
(const PLDHashTableOps* aOps), (aOps)) \
Macro(InternalUnwrapPLDHashTableCallbacks, const PLDHashTableOps*, \
(const PLDHashTableOps* aOps), (aOps)) \
Macro(AllocateMemory, void*, (size_t aSize, AllocatedMemoryKind aKind), (aSize, aKind)) \
Macro(InternalThingIndex, size_t, (void* aThing), (aThing)) \
Macro(InternalVirtualThingName, const char*, (void* aThing), (aThing)) \
Macro(NewCheckpoint, bool, (bool aTemporary), (aTemporary)) \
Macro(SpewEnabled, bool, (), ())
Macro(ExecutionProgressCounter, ProgressCounter*, (), ()) \
Macro(IsInternalScript, bool, (const char* aURL), (aURL)) \
Macro(DefineRecordReplayControlObject, bool, (JSContext* aCx, JSObject* aObj), (aCx, aObj))
#define FOR_EACH_INTERFACE_VOID(Macro) \
Macro(InternalBeginOrderedAtomicAccess, (), ()) \
@ -50,7 +50,6 @@ namespace recordreplay {
Macro(InternalEndCaptureEventStacks, (), ()) \
Macro(InternalRecordReplayBytes, \
(void* aData, size_t aSize), (aData, aSize)) \
Macro(DisallowUnhandledDivergeFromRecording, (), ()) \
Macro(NotifyUnrecordedWait, \
(const std::function<void()>& aCallback), (aCallback)) \
Macro(MaybeWaitForCheckpointSave, (), ()) \
@ -60,16 +59,8 @@ namespace recordreplay {
Macro(InternalMovePLDHashTableContents, \
(const PLDHashTableOps* aFirstOps, const PLDHashTableOps* aSecondOps), \
(aFirstOps, aSecondOps)) \
Macro(SetCheckpointHooks, \
(BeforeCheckpointHook aBefore, AfterCheckpointHook aAfter), \
(aBefore, aAfter)) \
Macro(ResumeExecution, (), ()) \
Macro(RestoreCheckpointAndResume, (const CheckpointId& aId), (aId)) \
Macro(DivergeFromRecording, (), ()) \
Macro(DeallocateMemory, \
(void* aAddress, size_t aSize, AllocatedMemoryKind aKind), (aAddress, aSize, aKind)) \
Macro(SetWeakPointerJSRoot, \
(const void* aPtr, void* aJSObj), (aPtr, aJSObj)) \
(const void* aPtr, JSObject* aJSObj), (aPtr, aJSObj)) \
Macro(RegisterTrigger, \
(void* aObj, const std::function<void()>& aCallback), \
(aObj, aCallback)) \
@ -83,7 +74,13 @@ namespace recordreplay {
Macro(InternalRegisterThing, (void* aThing), (aThing)) \
Macro(InternalUnregisterThing, (void* aThing), (aThing)) \
Macro(InternalRecordReplayDirective, (long aDirective), (aDirective)) \
Macro(InternalPrint, (const char* aFormat, va_list aArgs), (aFormat, aArgs))
Macro(BeginContentParse, \
(const void* aToken, const char* aURL, const char* aContentType), \
(aToken, aURL, aContentType)) \
Macro(AddContentParseData, \
(const void* aToken, const char16_t* aBuffer, size_t aLength), \
(aToken, aBuffer, aLength)) \
Macro(EndContentParse, (const void* aToken), (aToken))
#define DECLARE_SYMBOL(aName, aReturnType, aFormals, _) \
static aReturnType (*gPtr ##aName) aFormals;

View File

@ -18,6 +18,8 @@
#include <stdarg.h>
struct PLDHashTableOps;
struct JSContext;
class JSObject;
namespace mozilla {
namespace recordreplay {
@ -201,7 +203,7 @@ static inline void MovePLDHashTableContents(const PLDHashTableOps* aFirstOps,
// Associate an arbitrary pointer with a JS object root while replaying. This
// is useful for replaying the behavior of weak pointers.
MFBT_API void SetWeakPointerJSRoot(const void* aPtr, /*JSObject*/void* aJSObj);
MFBT_API void SetWeakPointerJSRoot(const void* aPtr, JSObject* aJSObj);
// API for ensuring that a function executes at a consistent point when
// recording or replaying. This is primarily needed for finalizers and other
@ -323,160 +325,59 @@ static const char gProcessKindOption[] = "-recordReplayKind";
static const char gRecordingFileOption[] = "-recordReplayFile";
///////////////////////////////////////////////////////////////////////////////
// Devtools API
// JS interface
///////////////////////////////////////////////////////////////////////////////
// This interface is used by devtools C++ code (e.g. the JS Debugger) running
// in a child or middleman process.
// Get the counter used to keep track of how much progress JS execution has
// made while running on the main thread. Progress must advance whenever a JS
// function is entered or loop entry point is reached, so that no script
// location may be hit twice while the progress counter is the same. See
// JSControl.h for more.
typedef uint64_t ProgressCounter;
MFBT_API ProgressCounter* ExecutionProgressCounter();
// The ID of a checkpoint in a child process. Checkpoints are either normal or
// temporary. Normal checkpoints occur at the same point in the recording and
// all replays, while temporary checkpoints are not used while recording and
// may be at different points in different replays.
struct CheckpointId
static inline void
AdvanceExecutionProgressCounter()
{
// ID of the most recent normal checkpoint, which are numbered in sequence
// starting at FirstCheckpointId.
size_t mNormal;
++*ExecutionProgressCounter();
}
// Special IDs for normal checkpoints.
static const size_t Invalid = 0;
static const size_t First = 1;
// Return whether a script is internal to the record/replay infrastructure,
// may run non-deterministically between recording and replaying, and whose
// execution must not update the progress counter.
MFBT_API bool IsInternalScript(const char* aURL);
// How many temporary checkpoints have been generated since the most recent
// normal checkpoint, zero if this represents the normal checkpoint itself.
size_t mTemporary;
// Define a RecordReplayControl object on the specified global object, with
// methods specialized to the current recording/replaying or middleman process
// kind.
MFBT_API bool DefineRecordReplayControlObject(JSContext* aCx, JSObject* aObj);
explicit CheckpointId(size_t aNormal = Invalid, size_t aTemporary = 0)
: mNormal(aNormal), mTemporary(aTemporary)
{}
// Notify the infrastructure that some URL which contains JavaScript is
// being parsed. This is used to provide the complete contents of the URL to
// devtools code when it is inspecting the state of this process; that devtools
// code can't simply fetch the URL itself since it may have been changed since
// the recording was made or may no longer exist. The token for a parse may not
// be used in other parses until after EndContentParse() is called.
MFBT_API void BeginContentParse(const void* aToken,
const char* aURL, const char* aContentType);
inline bool operator==(const CheckpointId& o) const {
return mNormal == o.mNormal && mTemporary == o.mTemporary;
}
// Add some parse data to an existing content parse.
MFBT_API void AddContentParseData(const void* aToken,
const char16_t* aBuffer, size_t aLength);
inline bool operator!=(const CheckpointId& o) const {
return mNormal != o.mNormal || mTemporary != o.mTemporary;
}
};
// Mark a content parse as having completed.
MFBT_API void EndContentParse(const void* aToken);
// Signature for the hook called when running forward, immediately before
// hitting a normal or temporary checkpoint.
typedef void (*BeforeCheckpointHook)();
// Signature for the hook called immediately after hitting a normal or
// temporary checkpoint, either when running forward or after rewinding.
typedef void (*AfterCheckpointHook)(const CheckpointId& aCheckpoint);
// Set hooks to call when encountering checkpoints.
MFBT_API void SetCheckpointHooks(BeforeCheckpointHook aBeforeCheckpoint,
AfterCheckpointHook aAfterCheckpoint);
// When paused at a breakpoint or at a checkpoint, unpause and proceed with
// execution.
MFBT_API void ResumeExecution();
// When paused at a breakpoint or at a checkpoint, restore a checkpoint that
// was saved earlier and resume execution.
MFBT_API void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint);
// Allow execution after this point to diverge from the recording. Execution
// will remain diverged until an earlier checkpoint is restored.
//
// If an unhandled divergence occurs (see the 'Recording Divergence' comment
// in ProcessRewind.h) then the process rewinds to the most recent saved
// checkpoint.
MFBT_API void DivergeFromRecording();
// After a call to DivergeFromRecording(), this may be called to prevent future
// unhandled divergence from causing earlier checkpoints to be restored
// (the process will immediately crash instead). This state lasts until a new
// call to DivergeFromRecording, or to an explicit restore of an earlier
// checkpoint.
MFBT_API void DisallowUnhandledDivergeFromRecording();
// Note a checkpoint at the current execution position. This checkpoint will be
// saved if either (a) it is temporary, or (b) the middleman has instructed
// this process to save this normal checkpoint. This method returns true if the
// checkpoint was just saved, and false if it was just restored.
MFBT_API bool NewCheckpoint(bool aTemporary);
// Print information about record/replay state. Printing is independent from
// the recording and will be printed by any recording, replaying, or middleman
// process. Spew is only printed when enabled via the RECORD_REPLAY_SPEW
// environment variable.
static inline void Print(const char* aFormat, ...);
static inline void PrintSpew(const char* aFormat, ...);
MFBT_API bool SpewEnabled();
///////////////////////////////////////////////////////////////////////////////
// Allocation policies
///////////////////////////////////////////////////////////////////////////////
// Type describing what kind of memory to allocate/deallocate by APIs below.
// TrackedMemoryKind is reserved for memory that is saved and restored when
// saving or restoring checkpoints. All other values refer to memory that is
// untracked, and whose contents are preserved when restoring checkpoints.
// Different values are used to distinguish different classes of memory for
// diagnosing leaks and reporting memory usage.
typedef size_t AllocatedMemoryKind;
static const AllocatedMemoryKind TrackedMemoryKind = 0;
// Memory kind to use for untracked debugger memory.
static const AllocatedMemoryKind DebuggerAllocatedMemoryKind = 1;
// Allocate or deallocate a block of memory of a particular kind. Allocated
// memory is initially zeroed.
MFBT_API void* AllocateMemory(size_t aSize, AllocatedMemoryKind aKind);
MFBT_API void DeallocateMemory(void* aAddress, size_t aSize, AllocatedMemoryKind aKind);
// Allocation policy for managing memory of a particular kind.
template <AllocatedMemoryKind Kind>
class AllocPolicy
// Perform an entire content parse, when the entire URL is available at once.
static inline void
NoteContentParse(const void* aToken,
const char* aURL, const char* aContentType,
const char16_t* aBuffer, size_t aLength)
{
public:
template <typename T>
T* maybe_pod_calloc(size_t aNumElems) {
if (aNumElems & tl::MulOverflowMask<sizeof(T)>::value) {
MOZ_CRASH();
}
// Note: AllocateMemory always returns zeroed memory.
return static_cast<T*>(AllocateMemory(aNumElems * sizeof(T), Kind));
}
template <typename T>
void free_(T* aPtr, size_t aSize) {
DeallocateMemory(aPtr, aSize * sizeof(T), Kind);
}
template <typename T>
T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
T* res = maybe_pod_calloc<T>(aNewSize);
memcpy(res, aPtr, aOldSize * sizeof(T));
free_<T>(aPtr, aOldSize);
return res;
}
template <typename T>
T* maybe_pod_malloc(size_t aNumElems) { return maybe_pod_calloc<T>(aNumElems); }
template <typename T>
T* pod_malloc(size_t aNumElems) { return maybe_pod_malloc<T>(aNumElems); }
template <typename T>
T* pod_calloc(size_t aNumElems) { return maybe_pod_calloc<T>(aNumElems); }
template <typename T>
T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
}
void reportAllocOverflow() const {}
MOZ_MUST_USE bool checkSimulatedOOM() const {
return true;
}
};
BeginContentParse(aToken, aURL, aContentType);
AddContentParseData(aToken, aBuffer, aLength);
EndContentParse(aToken);
}
///////////////////////////////////////////////////////////////////////////////
// API inline function implementation
@ -571,25 +472,6 @@ RecordReplayAssert(const char* aFormat, ...)
}
}
MFBT_API void InternalPrint(const char* aFormat, va_list aArgs);
#define MOZ_MakeRecordReplayPrinter(aName, aSpewing) \
static inline void \
aName(const char* aFormat, ...) \
{ \
if ((IsRecordingOrReplaying() || IsMiddleman()) && (!aSpewing || SpewEnabled())) { \
va_list ap; \
va_start(ap, aFormat); \
InternalPrint(aFormat, ap); \
va_end(ap); \
} \
}
MOZ_MakeRecordReplayPrinter(Print, false)
MOZ_MakeRecordReplayPrinter(PrintSpew, true)
#undef MOZ_MakeRecordReplayPrinter
} // recordreplay
} // mozilla

View File

@ -194,6 +194,30 @@ VectorAddOrRemoveEntry(Vector& aVector, const Entry& aEntry, bool aAdding)
aVector.append(aEntry);
}
bool SpewEnabled();
void InternalPrint(const char* aFormat, va_list aArgs);
#define MOZ_MakeRecordReplayPrinter(aName, aSpewing) \
static inline void \
aName(const char* aFormat, ...) \
{ \
if ((IsRecordingOrReplaying() || IsMiddleman()) && (!aSpewing || SpewEnabled())) { \
va_list ap; \
va_start(ap, aFormat); \
InternalPrint(aFormat, ap); \
va_end(ap); \
} \
}
// Print information about record/replay state. Printing is independent from
// the recording and will be printed by any recording, replaying, or middleman
// process. Spew is only printed when enabled via the RECORD_REPLAY_SPEW
// environment variable.
MOZ_MakeRecordReplayPrinter(Print, false)
MOZ_MakeRecordReplayPrinter(PrintSpew, true)
#undef MOZ_MakeRecordReplayPrinter
///////////////////////////////////////////////////////////////////////////////
// Profiling
///////////////////////////////////////////////////////////////////////////////
@ -225,26 +249,95 @@ private:
void DumpTimers();
// Different kinds of untracked memory used in the system.
namespace UntrackedMemoryKind {
// Note: 0 is TrackedMemoryKind, 1 is DebuggerAllocatedMemoryKind.
static const AllocatedMemoryKind Generic = 2;
///////////////////////////////////////////////////////////////////////////////
// Memory Management
///////////////////////////////////////////////////////////////////////////////
// Memory used by untracked files.
static const AllocatedMemoryKind File = 3;
// In cases where memory is tracked and should be saved/restored with
// checkoints, malloc and other standard library functions suffice to allocate
// memory in the record/replay system. The routines below are used for handling
// redirections for the raw system calls underlying the standard libraries, and
// for cases where allocated memory should be untracked: the contents are
// ignored when saving/restoring checkpoints.
// Different kinds of memory used in the system.
enum class MemoryKind {
// Memory whose contents are saved/restored with checkpoints.
Tracked,
// All remaining memory kinds refer to untracked memory.
// Memory not fitting into one of the categories below.
Generic,
// Memory used for thread snapshots.
static const AllocatedMemoryKind ThreadSnapshot = 4;
ThreadSnapshot,
// Memory used by various parts of the memory snapshot system.
static const AllocatedMemoryKind TrackedRegions = 5;
static const AllocatedMemoryKind FreeRegions = 6;
static const AllocatedMemoryKind DirtyPageSet = 7;
static const AllocatedMemoryKind SortedDirtyPageSet = 8;
static const AllocatedMemoryKind PageCopy = 9;
TrackedRegions,
FreeRegions,
DirtyPageSet,
SortedDirtyPageSet,
PageCopy,
static const size_t Count = 10;
}
// Memory used for navigation state.
Navigation,
Count
};
// Allocate or deallocate a block of memory of a particular kind. Allocated
// memory is initially zeroed.
void* AllocateMemory(size_t aSize, MemoryKind aKind);
void DeallocateMemory(void* aAddress, size_t aSize, MemoryKind aKind);
// Allocation policy for managing memory of a particular kind.
template <MemoryKind Kind>
class AllocPolicy
{
public:
template <typename T>
T* maybe_pod_calloc(size_t aNumElems) {
if (aNumElems & tl::MulOverflowMask<sizeof(T)>::value) {
MOZ_CRASH();
}
// Note: AllocateMemory always returns zeroed memory.
return static_cast<T*>(AllocateMemory(aNumElems * sizeof(T), Kind));
}
template <typename T>
void free_(T* aPtr, size_t aSize) {
DeallocateMemory(aPtr, aSize * sizeof(T), Kind);
}
template <typename T>
T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
T* res = maybe_pod_calloc<T>(aNewSize);
memcpy(res, aPtr, aOldSize * sizeof(T));
free_<T>(aPtr, aOldSize);
return res;
}
template <typename T>
T* maybe_pod_malloc(size_t aNumElems) { return maybe_pod_calloc<T>(aNumElems); }
template <typename T>
T* pod_malloc(size_t aNumElems) { return maybe_pod_malloc<T>(aNumElems); }
template <typename T>
T* pod_calloc(size_t aNumElems) { return maybe_pod_calloc<T>(aNumElems); }
template <typename T>
T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
}
void reportAllocOverflow() const {}
MOZ_MUST_USE bool checkSimulatedOOM() const {
return true;
}
};
///////////////////////////////////////////////////////////////////////////////
// Redirection Bypassing
@ -258,7 +351,7 @@ namespace UntrackedMemoryKind {
// Generic typedef for a system file handle.
typedef size_t FileHandle;
// Allocate/deallocate a block of memory.
// Allocate/deallocate a block of memory directly from the system.
void* DirectAllocateMemory(void* aAddress, size_t aSize);
void DirectDeallocateMemory(void* aAddress, size_t aSize);

View File

@ -79,6 +79,42 @@ namespace recordreplay {
// rewind.
///////////////////////////////////////////////////////////////////////////////
// The ID of a checkpoint in a child process. Checkpoints are either normal or
// temporary. Normal checkpoints occur at the same point in the recording and
// all replays, while temporary checkpoints are not used while recording and
// may be at different points in different replays.
struct CheckpointId
{
// ID of the most recent normal checkpoint, which are numbered in sequence
// starting at FirstCheckpointId.
size_t mNormal;
// Special IDs for normal checkpoints.
static const size_t Invalid = 0;
static const size_t First = 1;
// How many temporary checkpoints have been generated since the most recent
// normal checkpoint, zero if this represents the normal checkpoint itself.
size_t mTemporary;
explicit CheckpointId(size_t aNormal = Invalid, size_t aTemporary = 0)
: mNormal(aNormal), mTemporary(aTemporary)
{}
inline bool operator==(const CheckpointId& o) const {
return mNormal == o.mNormal && mTemporary == o.mTemporary;
}
inline bool operator!=(const CheckpointId& o) const {
return mNormal != o.mNormal || mTemporary != o.mTemporary;
}
CheckpointId NextCheckpoint(bool aTemporary) const {
return CheckpointId(aTemporary ? mNormal : mNormal + 1,
aTemporary ? mTemporary + 1 : 0);
}
};
// Initialize state needed for rewinding.
void InitializeRewindState();
@ -105,6 +141,29 @@ bool HasSavedCheckpoint();
// Get the ID of the most recent saved checkpoint.
CheckpointId GetLastSavedCheckpoint();
// When paused at a breakpoint or at a checkpoint, restore a checkpoint that
// was saved earlier and resume execution.
void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint);
// When paused at a breakpoint or at a checkpoint, unpause and proceed with
// execution.
void ResumeExecution();
// Allow execution after this point to diverge from the recording. Execution
// will remain diverged until an earlier checkpoint is restored.
//
// If an unhandled divergence occurs (see the 'Recording Divergence' comment
// in ProcessRewind.h) then the process rewinds to the most recent saved
// checkpoint.
void DivergeFromRecording();
// After a call to DivergeFromRecording(), this may be called to prevent future
// unhandled divergence from causing earlier checkpoints to be restored
// (the process will immediately crash instead). This state lasts until a new
// call to DivergeFromRecording, or to an explicit restore of an earlier
// checkpoint.
void DisallowUnhandledDivergeFromRecording();
// Make sure that execution has not diverged from the recording after a call to
// DivergeFromRecording, by rewinding to the last saved checkpoint if so.
void EnsureNotDivergedFromRecording();
@ -113,6 +172,12 @@ void EnsureNotDivergedFromRecording();
void SetIsActiveChild(bool aActive);
bool IsActiveChild();
// Note a checkpoint at the current execution position. This checkpoint will be
// saved if either (a) it is temporary, or (b) the middleman has instructed
// this process to save this normal checkpoint. This method returns true if the
// checkpoint was just saved, and false if it was just restored.
bool NewCheckpoint(bool aTemporary);
} // namespace recordreplay
} // namespace mozilla

View File

@ -13,7 +13,6 @@
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/ipc/ScopedXREEmbed.h"
#include "mozilla/ipc/Shmem.h"
#include "js/ReplayHooks.h"
namespace mozilla {
namespace recordreplay {

View File

@ -51,6 +51,19 @@ void Shutdown();
// Monitor used for synchronizing between the main and channel or message loop threads.
static Monitor* gMonitor;
// Allow the child process to resume execution.
void Resume(bool aForward);
// Pause the child process at the next opportunity.
void Pause();
// Send a JSON request to the child process, and synchronously wait for a
// response.
void SendRequest(const js::CharBuffer& aBuffer, js::CharBuffer* aResponse);
// Set or clear a breakpoint in the child process.
void SetBreakpoint(size_t aId, const js::BreakpointPosition& aPosition);
///////////////////////////////////////////////////////////////////////////////
// Graphics
///////////////////////////////////////////////////////////////////////////////
@ -258,7 +271,7 @@ public:
// Return whether this process is paused at a breakpoint whose kind matches
// the supplied filter.
typedef std::function<bool(JS::replay::ExecutionPosition::Kind)> BreakpointFilter;
typedef std::function<bool(js::BreakpointPosition::Kind)> BreakpointFilter;
bool IsPausedAtMatchingBreakpoint(const BreakpointFilter& aFilter);
// Get the checkpoint at or earlier to the process' position. This is either