Bug 1547084 Part 3 - C++ changes and removal for new control logic, r=loganfsmyth.

--HG--
extra : rebase_source : e5c9c1aa48b8657b71527dce273feddc57bd0e3b
This commit is contained in:
Brian Hackett 2019-05-12 13:16:36 -10:00
Родитель c201146e83
Коммит 96163d0830
22 изменённых файлов: 616 добавлений и 2685 удалений

Просмотреть файл

@ -41,7 +41,7 @@ void BeginCallback(size_t aCallbackId) {
Thread* thread = Thread::Current();
if (thread->IsMainThread()) {
child::EndIdleTime();
js::EndIdleTime();
}
thread->SetPassThrough(false);
@ -59,7 +59,7 @@ void EndCallback() {
Thread* thread = Thread::Current();
if (thread->IsMainThread()) {
child::BeginIdleTime();
js::BeginIdleTime();
}
thread->SetPassThrough(true);
}
@ -99,12 +99,12 @@ void PassThroughThreadEventsAllowCallbacks(const std::function<void()>& aFn) {
if (IsRecording()) {
if (thread->IsMainThread()) {
child::BeginIdleTime();
js::BeginIdleTime();
}
thread->SetPassThrough(true);
aFn();
if (thread->IsMainThread()) {
child::EndIdleTime();
js::EndIdleTime();
}
thread->SetPassThrough(false);
thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CallbacksFinished);

Просмотреть файл

@ -161,7 +161,7 @@ typedef SplayTree<DirtyPage, DirtyPage::AddressSort,
// A set of dirty pages associated with some checkpoint.
struct DirtyPageSet {
// Checkpoint associated with this set.
CheckpointId mCheckpoint;
size_t mCheckpoint;
// All dirty pages in the set. Pages may be added or destroyed by the main
// thread when all other threads are idle, by the dirty memory handler when
@ -170,7 +170,7 @@ struct DirtyPageSet {
InfallibleVector<DirtyPage, 256, AllocPolicy<MemoryKind::DirtyPageSet>>
mPages;
explicit DirtyPageSet(const CheckpointId& aCheckpoint)
explicit DirtyPageSet(size_t aCheckpoint)
: mCheckpoint(aCheckpoint) {}
};
@ -349,17 +349,10 @@ struct MemoryInfo {
// Recent dirty memory faults.
void* mDirtyMemoryFaults[50];
// Whether RecordReplayDirective may crash this process.
bool mIntentionalCrashesAllowed;
// Whether the CrashSoon directive has been given to this process.
bool mCrashSoon;
MemoryInfo()
: mMemoryChangesAllowed(true),
mFreeUntrackedRegions(MemoryKind::FreeRegions),
mStartTime(CurrentTime()),
mIntentionalCrashesAllowed(true) {
mStartTime(CurrentTime()) {
// The singleton MemoryInfo is allocated with zeroed memory, so other
// fields do not need explicit initialization.
}
@ -431,42 +424,6 @@ void DumpTimers() {
}
}
///////////////////////////////////////////////////////////////////////////////
// Directives
///////////////////////////////////////////////////////////////////////////////
void SetAllowIntentionalCrashes(bool aAllowed) {
gMemoryInfo->mIntentionalCrashesAllowed = aAllowed;
}
extern "C" {
MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayDirective(
long aDirective) {
switch ((Directive)aDirective) {
case Directive::CrashSoon:
gMemoryInfo->mCrashSoon = true;
break;
case Directive::MaybeCrash:
if (gMemoryInfo->mIntentionalCrashesAllowed && gMemoryInfo->mCrashSoon) {
PrintSpew("Intentionally Crashing!\n");
MOZ_CRASH("RecordReplayDirective intentional crash");
}
gMemoryInfo->mCrashSoon = false;
break;
case Directive::AlwaysSaveTemporaryCheckpoints:
navigation::AlwaysSaveTemporaryCheckpoints();
break;
case Directive::AlwaysMarkMajorCheckpoints:
child::NotifyAlwaysMarkMajorCheckpoints();
break;
default:
MOZ_CRASH("Unknown directive");
}
}
} // extern "C"
///////////////////////////////////////////////////////////////////////////////
// Snapshot Thread Conditions
///////////////////////////////////////////////////////////////////////////////
@ -645,7 +602,7 @@ void UnrecoverableSnapshotFailure() {
///////////////////////////////////////////////////////////////////////////////
void AddInitialUntrackedMemoryRegion(uint8_t* aBase, size_t aSize) {
MOZ_RELEASE_ASSERT(!HasSavedCheckpoint());
MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint());
if (gInitializationFailureMessage) {
return;
@ -675,7 +632,7 @@ void AddInitialUntrackedMemoryRegion(uint8_t* aBase, size_t aSize) {
}
static void RemoveInitialUntrackedRegion(uint8_t* aBase, size_t aSize) {
MOZ_RELEASE_ASSERT(!HasSavedCheckpoint());
MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint());
AutoSpinLock lock(gMemoryInfo->mInitialUntrackedRegionsLock);
for (AllocatedMemoryRegion& region : gMemoryInfo->mInitialUntrackedRegions) {
@ -1042,10 +999,10 @@ void RegisterAllocatedMemory(void* aBaseAddress, size_t aSize,
uint8_t* aAddress = reinterpret_cast<uint8_t*>(aBaseAddress);
if (aKind != MemoryKind::Tracked) {
if (!HasSavedCheckpoint()) {
if (!HasSavedAnyCheckpoint()) {
AddInitialUntrackedMemoryRegion(aAddress, aSize);
}
} else if (HasSavedCheckpoint()) {
} else if (HasSavedAnyCheckpoint()) {
EnsureMemoryChangesAllowed();
DirectWriteProtectMemory(aAddress, aSize, true);
AddTrackedRegion(aAddress, aSize, true);
@ -1056,7 +1013,7 @@ void CheckFixedMemory(void* aAddress, size_t aSize) {
MOZ_RELEASE_ASSERT(aAddress == PageBase(aAddress));
MOZ_RELEASE_ASSERT(aSize == RoundupSizeToPageBoundary(aSize));
if (!HasSavedCheckpoint()) {
if (!HasSavedAnyCheckpoint()) {
return;
}
@ -1087,7 +1044,7 @@ void RestoreWritableFixedMemory(void* aAddress, size_t aSize) {
MOZ_RELEASE_ASSERT(aAddress == PageBase(aAddress));
MOZ_RELEASE_ASSERT(aSize == RoundupSizeToPageBoundary(aSize));
if (!HasSavedCheckpoint()) {
if (!HasSavedAnyCheckpoint()) {
return;
}
@ -1108,7 +1065,7 @@ void* AllocateMemoryTryAddress(void* aAddress, size_t aSize, MemoryKind aKind) {
gMemoryInfo->mMemoryBalance[(size_t)aKind] += aSize;
}
if (HasSavedCheckpoint()) {
if (HasSavedAnyCheckpoint()) {
if (void* res = FreeRegionSet::Get(aKind).Extract(aAddress, aSize)) {
return res;
}
@ -1141,7 +1098,7 @@ void DeallocateMemory(void* aAddress, size_t aSize, MemoryKind aKind) {
}
// Memory is returned to the system before saving the first checkpoint.
if (!HasSavedCheckpoint()) {
if (!HasSavedAnyCheckpoint()) {
if (IsReplaying() && aKind != MemoryKind::Tracked) {
RemoveInitialUntrackedRegion((uint8_t*)aAddress, aSize);
}
@ -1172,7 +1129,7 @@ void DeallocateMemory(void* aAddress, size_t aSize, MemoryKind aKind) {
// this thread which were modified since the last recorded diff snapshot.
static void SnapshotThreadRestoreLastDiffSnapshot(
SnapshotThreadWorklist* aWorklist) {
CheckpointId checkpoint = GetLastSavedCheckpoint();
size_t checkpoint = GetLastSavedCheckpoint();
DirtyPageSet& set = aWorklist->mSets.back();
MOZ_RELEASE_ASSERT(set.mCheckpoint == checkpoint);

Просмотреть файл

@ -102,10 +102,6 @@ void UnrecoverableSnapshotFailure();
// was taken as free.
void FixupFreeRegionsAfterRewind();
// Set whether to allow intentionally crashing in this process via the
// RecordReplayDirective method.
void SetAllowIntentionalCrashes(bool aAllowed);
// When WANT_COUNTDOWN_THREAD is defined (see MemorySnapshot.cpp), set a count
// that, after a thread consumes it, causes the thread to report a fatal error.
// This is used for debugging and is a workaround for lldb often being unable

Просмотреть файл

@ -56,6 +56,9 @@ static int gRecordingPid;
// Whether to spew record/replay messages to stderr.
static bool gSpewEnabled;
// Whether this is the main child.
static bool gMainChild;
extern "C" {
MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) {
@ -95,16 +98,20 @@ MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) {
MOZ_CRASH("Bad ProcessKind");
}
if (IsRecordingOrReplaying() && TestEnv("WAIT_AT_START")) {
if (IsRecording() && TestEnv("MOZ_RECORDING_WAIT_AT_START")) {
BusyWait();
}
if (IsMiddleman() && TestEnv("MIDDLEMAN_WAIT_AT_START")) {
if (IsReplaying() && TestEnv("MOZ_REPLAYING_WAIT_AT_START")) {
BusyWait();
}
if (IsMiddleman() && TestEnv("MOZ_MIDDLEMAN_WAIT_AT_START")) {
BusyWait();
}
gPid = getpid();
if (TestEnv("RECORD_REPLAY_SPEW")) {
if (TestEnv("MOZ_RECORD_REPLAY_SPEW")) {
gSpewEnabled = true;
}
@ -159,6 +166,8 @@ MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) {
InitializeRewindState();
gRecordingPid = RecordReplayValue(gPid);
gMainChild = IsRecording();
gInitialized = true;
}
@ -201,25 +210,18 @@ MOZ_EXPORT void RecordReplayInterface_InternalInvalidateRecording(
} // extern "C"
// How many recording endpoints have been flushed to the recording.
static size_t gNumEndpoints;
void FlushRecording() {
MOZ_RELEASE_ASSERT(IsRecording());
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
// Save the endpoint of the recording.
js::ExecutionPoint endpoint = navigation::GetRecordingEndpoint();
// The recording can only be flushed when we are at a checkpoint.
// Save this endpoint to the recording.
size_t endpoint = GetLastCheckpoint();
Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
endpointStream->WriteScalar(++gNumEndpoints);
endpointStream->WriteBytes(&endpoint, sizeof(endpoint));
endpointStream->WriteScalar(endpoint);
gRecordingFile->PreventStreamWrites();
gRecordingFile->Flush();
child::NotifyFlushedRecording();
gRecordingFile->AllowStreamWrites();
}
@ -246,34 +248,6 @@ static bool LoadNextRecordingIndex() {
return found;
}
bool HitRecordingEndpoint() {
MOZ_RELEASE_ASSERT(IsReplaying());
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
// The debugger will call this method in a loop, so we don't have to do
// anything fancy to try to get the most up to date endpoint. As long as we
// can make some progress in attempting to find a later endpoint, we can
// return control to the debugger.
// Check if there is a new endpoint in the endpoint data stream.
Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
if (!endpointStream->AtEnd()) {
js::ExecutionPoint endpoint;
size_t index = endpointStream->ReadScalar();
endpointStream->ReadBytes(&endpoint, sizeof(endpoint));
navigation::SetRecordingEndpoint(index, endpoint);
return true;
}
// Check if there is more data in the recording.
if (LoadNextRecordingIndex()) {
return true;
}
// OK, we hit the most up to date endpoint in the recording.
return false;
}
void HitEndOfRecording() {
MOZ_RELEASE_ASSERT(IsReplaying());
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
@ -290,6 +264,21 @@ void HitEndOfRecording() {
}
}
// When replaying, the last endpoint loaded from the recording.
static size_t gRecordingEndpoint;
size_t RecordingEndpoint() {
MOZ_RELEASE_ASSERT(IsReplaying());
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
while (!endpointStream->AtEnd()) {
gRecordingEndpoint = endpointStream->ReadScalar();
}
return gRecordingEndpoint;
}
bool SpewEnabled() { return gSpewEnabled; }
void InternalPrint(const char* aFormat, va_list aArgs) {
@ -315,6 +304,9 @@ const char* ThreadEventName(ThreadEvent aEvent) {
int GetRecordingPid() { return gRecordingPid; }
bool IsMainChild() { return gMainChild; }
void SetMainChild() { gMainChild = true; }
///////////////////////////////////////////////////////////////////////////////
// Record/Replay Assertions
///////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -107,24 +107,13 @@ void FlushRecording();
// Called when any thread hits the end of its event stream.
void HitEndOfRecording();
// Called when the main thread hits the latest recording endpoint it knows
// about.
bool HitRecordingEndpoint();
// Called in a replaying process to load the last checkpoint in the recording.
size_t RecordingEndpoint();
// Possible directives to give via the RecordReplayDirective function.
enum class Directive {
// Crash at the next use of MaybeCrash.
CrashSoon = 1,
// Irrevocably crash if CrashSoon has ever been used on the process.
MaybeCrash = 2,
// Always save temporary checkpoints when stepping around in the debugger.
AlwaysSaveTemporaryCheckpoints = 3,
// Mark all future checkpoints as major checkpoints in the middleman.
AlwaysMarkMajorCheckpoints = 4
};
// Access the flag for whether this is the main child. The main child never
// rewinds and sends graphics updates to the middleman while running forward.
bool IsMainChild();
void SetMainChild();
// Get the process kind and recording file specified at the command line.
// These are available in the middleman as well as while recording/replaying.
@ -283,8 +272,8 @@ enum class MemoryKind {
SortedDirtyPageSet,
PageCopy,
// Memory used for navigation state.
Navigation,
// Memory used by various parts of JS integration.
ScriptHits,
Count
};

Просмотреть файл

@ -404,7 +404,7 @@ static PreambleResult MiddlemanPreamble_sendmsg(CallArguments* aArguments) {
static PreambleResult Preamble_mprotect(CallArguments* aArguments) {
// Ignore any mprotect calls that occur after saving a checkpoint.
if (!HasSavedCheckpoint()) {
if (!HasSavedAnyCheckpoint()) {
return PreambleResult::PassThrough;
}
aArguments->Rval<ssize_t>() = 0;
@ -432,7 +432,7 @@ static PreambleResult Preamble_mmap(CallArguments* aArguments) {
// Get an anonymous mapping for the result.
if (flags & MAP_FIXED) {
// For fixed allocations, make sure this memory region is mapped and zero.
if (!HasSavedCheckpoint()) {
if (!HasSavedAnyCheckpoint()) {
// Make sure this memory region is writable.
CallFunction<int>(gOriginal_mprotect, address, size,
PROT_READ | PROT_WRITE | PROT_EXEC);
@ -448,7 +448,7 @@ static PreambleResult Preamble_mmap(CallArguments* aArguments) {
// for memory that is already allocated. If we haven't saved a checkpoint
// then this is no problem, but after saving a checkpoint we have to make
// sure that protection flags are what we expect them to be.
int newProt = HasSavedCheckpoint() ? (PROT_READ | PROT_EXEC) : prot;
int newProt = HasSavedAnyCheckpoint() ? (PROT_READ | PROT_EXEC) : prot;
memory = CallFunction<void*>(gOriginal_mmap, address, size, newProt, flags,
fd, offset);
@ -674,9 +674,11 @@ static ssize_t WaitForCvar(pthread_mutex_t* aMutex, pthread_cond_t* aCond,
static PreambleResult Preamble_pthread_cond_wait(CallArguments* aArguments) {
auto& cond = aArguments->Arg<0, pthread_cond_t*>();
auto& mutex = aArguments->Arg<1, pthread_mutex_t*>();
js::BeginIdleTime();
aArguments->Rval<ssize_t>() = WaitForCvar(mutex, cond, false, [=]() {
return CallFunction<ssize_t>(gOriginal_pthread_cond_wait, cond, mutex);
});
js::EndIdleTime();
return PreambleResult::Veto;
}
@ -925,7 +927,7 @@ static PreambleResult Preamble_mach_vm_map(CallArguments* aArguments) {
} else if (AreThreadEventsPassedThrough()) {
// We should only reach this at startup, when initializing the graphics
// shared memory block.
MOZ_RELEASE_ASSERT(!HasSavedCheckpoint());
MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint());
return PreambleResult::PassThrough;
}
@ -940,7 +942,7 @@ static PreambleResult Preamble_mach_vm_map(CallArguments* aArguments) {
static PreambleResult Preamble_mach_vm_protect(CallArguments* aArguments) {
// Ignore any mach_vm_protect calls that occur after saving a checkpoint, as
// for mprotect.
if (!HasSavedCheckpoint()) {
if (!HasSavedAnyCheckpoint()) {
return PreambleResult::PassThrough;
}
aArguments->Rval<size_t>() = KERN_SUCCESS;

Просмотреть файл

@ -24,11 +24,7 @@ namespace recordreplay {
// are in untracked memory.
struct RewindInfo {
// The most recent checkpoint which was encountered.
CheckpointId mLastCheckpoint;
// Whether this is the active child process. See the comment under
// 'Child Roles' in ParentIPC.cpp.
bool mIsActiveChild;
size_t mLastCheckpoint;
// Checkpoints which have been saved. This includes only entries from
// mShouldSaveCheckpoints, plus all temporary checkpoints.
@ -55,22 +51,23 @@ void InitializeRewindState() {
void* memory = AllocateMemory(sizeof(RewindInfo), MemoryKind::Generic);
gRewindInfo = new (memory) RewindInfo();
// The first checkpoint is implicitly saved while replaying: we won't be able
// to get a manifest from the middleman telling us what to save until after
// this checkpoint has been reached.
if (IsReplaying()) {
gRewindInfo->mShouldSaveCheckpoints.append(FirstCheckpointId);
}
gMainThreadCallbackMonitor = new Monitor();
}
static bool CheckpointPrecedes(const CheckpointId& aFirst,
const CheckpointId& aSecond) {
return aFirst.mNormal < aSecond.mNormal ||
aFirst.mTemporary < aSecond.mTemporary;
}
void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) {
void RestoreCheckpointAndResume(size_t aCheckpoint) {
MOZ_RELEASE_ASSERT(IsReplaying());
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
MOZ_RELEASE_ASSERT(
aCheckpoint == gRewindInfo->mLastCheckpoint ||
CheckpointPrecedes(aCheckpoint, gRewindInfo->mLastCheckpoint));
aCheckpoint < gRewindInfo->mLastCheckpoint);
// Make sure we don't lose pending main thread callbacks due to rewinding.
{
@ -85,10 +82,10 @@ void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) {
{
// Rewind heap memory to the target checkpoint, which must have been saved.
AutoDisallowMemoryChanges disallow;
CheckpointId newCheckpoint =
size_t newCheckpoint =
gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
RestoreMemoryToLastSavedCheckpoint();
while (CheckpointPrecedes(aCheckpoint, newCheckpoint)) {
while (aCheckpoint < newCheckpoint) {
gRewindInfo->mSavedCheckpoints.back().ReleaseContents();
gRewindInfo->mSavedCheckpoints.popBack();
RestoreMemoryToLastSavedDiffCheckpoint();
@ -100,10 +97,8 @@ void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) {
FixupFreeRegionsAfterRewind();
double end = CurrentTime();
PrintSpew("Restore #%d:%d -> #%d:%d %.2fs\n",
(int)gRewindInfo->mLastCheckpoint.mNormal,
(int)gRewindInfo->mLastCheckpoint.mTemporary,
(int)aCheckpoint.mNormal, (int)aCheckpoint.mTemporary,
PrintSpew("Restore #%d -> #%d %.2fs\n",
(int)gRewindInfo->mLastCheckpoint, (int)aCheckpoint,
(end - start) / 1000000.0);
// Finally, let threads restore themselves to their stacks at the checkpoint
@ -113,27 +108,24 @@ void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) {
}
void SetSaveCheckpoint(size_t aCheckpoint, bool aSave) {
MOZ_RELEASE_ASSERT(aCheckpoint > gRewindInfo->mLastCheckpoint.mNormal);
MOZ_RELEASE_ASSERT(aCheckpoint > gRewindInfo->mLastCheckpoint);
VectorAddOrRemoveEntry(gRewindInfo->mShouldSaveCheckpoints, aCheckpoint,
aSave);
}
bool NewCheckpoint(bool aTemporary) {
bool NewCheckpoint() {
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
MOZ_RELEASE_ASSERT(IsReplaying() || !aTemporary);
navigation::BeforeCheckpoint();
js::BeforeCheckpoint();
// Get the ID of the new checkpoint.
CheckpointId checkpoint =
gRewindInfo->mLastCheckpoint.NextCheckpoint(aTemporary);
size_t checkpoint = gRewindInfo->mLastCheckpoint + 1;
// Save all checkpoints the middleman tells us to, and temporary checkpoints
// (which the middleman never knows about).
bool save = aTemporary || VectorContains(gRewindInfo->mShouldSaveCheckpoints,
checkpoint.mNormal);
bool save = VectorContains(gRewindInfo->mShouldSaveCheckpoints, checkpoint);
bool reachedCheckpoint = true;
if (save) {
@ -156,11 +148,10 @@ bool NewCheckpoint(bool aTemporary) {
// Save all thread stacks for the checkpoint. If we rewind here from a
// later point of execution then this will return false.
if (SaveAllThreads(gRewindInfo->mSavedCheckpoints.back())) {
PrintSpew("Saved checkpoint #%d:%d %.2fs\n", (int)checkpoint.mNormal,
(int)checkpoint.mTemporary, (end - start) / 1000000.0);
PrintSpew("Saved checkpoint #%d %.2fs\n", (int)checkpoint,
(end - start) / 1000000.0);
} else {
PrintSpew("Restored checkpoint #%d:%d\n", (int)checkpoint.mNormal,
(int)checkpoint.mTemporary);
PrintSpew("Restored checkpoint #%d\n", (int)checkpoint);
reachedCheckpoint = false;
@ -171,11 +162,13 @@ bool NewCheckpoint(bool aTemporary) {
}
Thread::ResumeIdleThreads();
} else {
PrintSpew("Skipping checkpoint #%d\n", (int)checkpoint);
}
gRewindInfo->mLastCheckpoint = checkpoint;
navigation::AfterCheckpoint(checkpoint);
js::AfterCheckpoint(checkpoint, !reachedCheckpoint);
return reachedCheckpoint;
}
@ -237,23 +230,29 @@ void EnsureNotDivergedFromRecording() {
}
}
bool HasSavedCheckpoint() {
bool HasSavedAnyCheckpoint() {
return gRewindInfo && !gRewindInfo->mSavedCheckpoints.empty();
}
CheckpointId GetLastSavedCheckpoint() {
MOZ_RELEASE_ASSERT(HasSavedCheckpoint());
return gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
}
CheckpointId GetLastSavedCheckpointPriorTo(const CheckpointId& aCheckpoint) {
MOZ_RELEASE_ASSERT(HasSavedCheckpoint());
for (size_t i = gRewindInfo->mSavedCheckpoints.length() - 1; i >= 1; i--) {
if (gRewindInfo->mSavedCheckpoints[i].mCheckpoint == aCheckpoint) {
return gRewindInfo->mSavedCheckpoints[i - 1].mCheckpoint;
bool HasSavedCheckpoint(size_t aCheckpoint) {
if (!gRewindInfo) {
return false;
}
for (const SavedCheckpoint& saved : gRewindInfo->mSavedCheckpoints) {
if (saved.mCheckpoint == aCheckpoint) {
return true;
}
}
MOZ_CRASH("GetLastSavedCheckpointPriorTo");
return false;
}
size_t GetLastCheckpoint() {
return gRewindInfo ? gRewindInfo->mLastCheckpoint : InvalidCheckpointId;
}
size_t GetLastSavedCheckpoint() {
MOZ_RELEASE_ASSERT(HasSavedAnyCheckpoint());
return gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
}
static bool gMainThreadShouldPause = false;
@ -262,7 +261,6 @@ bool MainThreadShouldPause() { return gMainThreadShouldPause; }
void PauseMainThreadAndServiceCallbacks() {
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
AssertEventsAreNotPassedThrough();
// Whether there is a PauseMainThreadAndServiceCallbacks frame on the stack.
@ -273,6 +271,8 @@ void PauseMainThreadAndServiceCallbacks() {
}
gMainThreadIsPaused = true;
MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
MonitorAutoLock lock(*gMainThreadCallbackMonitor);
// Loop and invoke callbacks until one of them unpauses this thread.
@ -320,9 +320,5 @@ void ResumeExecution() {
gMainThreadCallbackMonitor->Notify();
}
void SetIsActiveChild(bool aActive) { gRewindInfo->mIsActiveChild = aActive; }
bool IsActiveChild() { return gRewindInfo->mIsActiveChild; }
} // namespace recordreplay
} // namespace mozilla

Просмотреть файл

@ -79,39 +79,9 @@ 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);
}
};
// Special IDs for normal checkpoints.
static const size_t InvalidCheckpointId = 0;
static const size_t FirstCheckpointId = 1;
// Initialize state needed for rewinding.
void InitializeRewindState();
@ -134,17 +104,20 @@ bool MainThreadShouldPause();
void PauseMainThreadAndServiceCallbacks();
// Return whether any checkpoints have been saved.
bool HasSavedCheckpoint();
bool HasSavedAnyCheckpoint();
// Return whether a specific checkpoint has been saved.
bool HasSavedCheckpoint(size_t aCheckpoint);
// Get the ID of the most recently encountered checkpoint.
size_t GetLastCheckpoint();
// Get the ID of the most recent saved checkpoint.
CheckpointId GetLastSavedCheckpoint();
// Get the ID of the saved checkpoint prior to aCheckpoint.
CheckpointId GetLastSavedCheckpointPriorTo(const CheckpointId& aCheckpoint);
size_t 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);
void RestoreCheckpointAndResume(size_t aCheckpoint);
// When paused at a breakpoint or at a checkpoint, unpause and proceed with
// execution.
@ -169,15 +142,10 @@ void DisallowUnhandledDivergeFromRecording();
// DivergeFromRecording, by rewinding to the last saved checkpoint if so.
void EnsureNotDivergedFromRecording();
// Access the flag for whether this is the active child process.
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);
// saved if it was instructed to do so via a manifest. This method returns true
// if the checkpoint was just saved, and false if it was just restored.
bool NewCheckpoint();
} // namespace recordreplay
} // namespace mozilla

Просмотреть файл

@ -73,10 +73,10 @@ struct SavedThreadStack {
};
struct SavedCheckpoint {
CheckpointId mCheckpoint;
size_t mCheckpoint;
SavedThreadStack mStacks[MaxRecordedThreadId];
explicit SavedCheckpoint(CheckpointId aCheckpoint)
explicit SavedCheckpoint(size_t aCheckpoint)
: mCheckpoint(aCheckpoint) {}
void ReleaseContents() {

Просмотреть файл

@ -336,58 +336,19 @@ void Channel::PrintMessage(const char* aPrefix, const Message& aMsg) {
AutoEnsurePassThroughThreadEvents pt;
nsCString data;
switch (aMsg.mType) {
case MessageType::HitExecutionPoint: {
const HitExecutionPointMessage& nmsg =
(const HitExecutionPointMessage&)aMsg;
nmsg.mPoint.ToString(data);
data.AppendPrintf(" Endpoint %d Duration %.2f ms",
nmsg.mRecordingEndpoint,
nmsg.mDurationMicroseconds / 1000.0);
break;
}
case MessageType::Resume: {
const ResumeMessage& nmsg = (const ResumeMessage&)aMsg;
data.AppendPrintf("Forward %d", nmsg.mForward);
break;
}
case MessageType::RestoreCheckpoint: {
const RestoreCheckpointMessage& nmsg =
(const RestoreCheckpointMessage&)aMsg;
data.AppendPrintf("Id %d", (int)nmsg.mCheckpoint);
break;
}
case MessageType::AddBreakpoint: {
const AddBreakpointMessage& nmsg = (const AddBreakpointMessage&)aMsg;
data.AppendPrintf(
"Kind %s, Script %d, Offset %d, Frame %d",
nmsg.mPosition.KindString(), (int)nmsg.mPosition.mScript,
(int)nmsg.mPosition.mOffset, (int)nmsg.mPosition.mFrameIndex);
break;
}
case MessageType::DebuggerRequest: {
const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&)aMsg;
case MessageType::ManifestStart: {
const ManifestStartMessage& nmsg = (const ManifestStartMessage&)aMsg;
data = NS_ConvertUTF16toUTF8(
nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize()));
break;
}
case MessageType::DebuggerResponse: {
const DebuggerResponseMessage& nmsg =
(const DebuggerResponseMessage&)aMsg;
case MessageType::ManifestFinished: {
const ManifestFinishedMessage& nmsg =
(const ManifestFinishedMessage&)aMsg;
data = NS_ConvertUTF16toUTF8(
nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize()));
break;
}
case MessageType::SetIsActive: {
const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&)aMsg;
data.AppendPrintf("%d", nmsg.mActive);
break;
}
case MessageType::SetSaveCheckpoint: {
const SetSaveCheckpointMessage& nmsg =
(const SetSaveCheckpointMessage&)aMsg;
data.AppendPrintf("Id %d, Save %d", (int)nmsg.mCheckpoint, nmsg.mSave);
break;
}
default:
break;
}

Просмотреть файл

@ -42,13 +42,6 @@ namespace recordreplay {
// messages from being lost when they are sent from the middleman as the
// replaying process rewinds itself. A few exceptions to this rule are noted
// below.
//
// Some additional synchronization is needed between different child processes:
// replaying processes can read from the same file which a recording process is
// writing to. While it is ok for a replaying process to read from the file
// while the recording process is appending new chunks to it (see File.cpp),
// all replaying processes must be paused when the recording process is
// flushing a new index to the file.
#define ForEachMessageType(_Macro) \
/* Messages sent from the middleman to the child process. */ \
@ -64,56 +57,20 @@ namespace recordreplay {
/* process to crash. */ \
_Macro(Terminate) \
\
/* Flush the current recording to disk. */ \
_Macro(FlushRecording) \
\
/* Poke a child that is recording to create an artificial checkpoint, rather than */ \
/* (potentially) idling indefinitely. This has no effect on a replaying process. */ \
_Macro(CreateCheckpoint) \
\
/* Debugger JSON messages are initially sent from the parent. The child unpauses */ \
/* after receiving the message and will pause after it sends a DebuggerResponse. */ \
_Macro(DebuggerRequest) \
\
/* Add a breakpoint position to stop at. Because a single entry point is used for */ \
/* calling into the ReplayDebugger after pausing, the set of breakpoints is simply */ \
/* a set of positions at which the child process should pause and send a */ \
/* HitExecutionPoint message. */ \
_Macro(AddBreakpoint) \
\
/* Clear all installed breakpoints. */ \
_Macro(ClearBreakpoints) \
\
/* Unpause the child and play execution either to the next point when a */ \
/* breakpoint is hit, or to the next checkpoint. Resumption may be either */ \
/* forward or backward. */ \
_Macro(Resume) \
\
/* Rewind to a particular saved checkpoint in the past. */ \
_Macro(RestoreCheckpoint) \
\
/* Run forward to a particular execution point between the current checkpoint */ \
/* and the next one. */ \
_Macro(RunToPoint) \
\
/* Notify the child whether it is the active child and should send paint and similar */ \
/* messages to the middleman. */ \
_Macro(SetIsActive) \
\
/* Set whether to perform intentional crashes, for testing. */ \
_Macro(SetAllowIntentionalCrashes) \
\
/* Set whether to save a particular checkpoint. */ \
_Macro(SetSaveCheckpoint) \
/* Unpause the child and perform a debugger-defined operation. */ \
_Macro(ManifestStart) \
\
/* Respond to a MiddlemanCallRequest message. */ \
_Macro(MiddlemanCallResponse) \
\
/* Messages sent from the child process to the middleman. */ \
\
/* Sent in response to a FlushRecording, telling the middleman that the flush */ \
/* has finished. */ \
_Macro(RecordingFlushed) \
/* Pause after executing a manifest, specifying its response. */ \
_Macro(ManifestFinished) \
\
/* A critical error occurred and execution cannot continue. The child will */ \
/* stop executing after sending this message and will wait to be terminated. */ \
@ -127,21 +84,12 @@ namespace recordreplay {
/* The child's graphics were repainted. */ \
_Macro(Paint) \
\
/* Notify the middleman that the child has hit an execution point and paused. */ \
_Macro(HitExecutionPoint) \
\
/* Send a response to a DebuggerRequest message. */ \
_Macro(DebuggerResponse) \
\
/* Call a system function from the middleman process which the child has */ \
/* encountered after diverging from the recording. */ \
_Macro(MiddlemanCallRequest) \
\
/* Reset all information generated by previous MiddlemanCallRequest messages. */ \
_Macro(ResetMiddlemanCalls) \
\
/* Notify that the 'AlwaysMarkMajorCheckpoints' directive was invoked. */ \
_Macro(AlwaysMarkMajorCheckpoints)
_Macro(ResetMiddlemanCalls)
enum class MessageType {
#define DefineEnum(Kind) Kind,
@ -195,7 +143,8 @@ struct Message {
return mType == MessageType::CreateCheckpoint ||
mType == MessageType::SetDebuggerRunsInMiddleman ||
mType == MessageType::MiddlemanCallResponse ||
mType == MessageType::Terminate;
mType == MessageType::Terminate ||
mType == MessageType::Introduction;
}
protected:
@ -275,7 +224,6 @@ typedef EmptyMessage<MessageType::SetDebuggerRunsInMiddleman>
SetDebuggerRunsInMiddlemanMessage;
typedef EmptyMessage<MessageType::Terminate> TerminateMessage;
typedef EmptyMessage<MessageType::CreateCheckpoint> CreateCheckpointMessage;
typedef EmptyMessage<MessageType::FlushRecording> FlushRecordingMessage;
template <MessageType Type>
struct JSONMessage : public Message {
@ -293,75 +241,8 @@ struct JSONMessage : public Message {
}
};
typedef JSONMessage<MessageType::DebuggerRequest> DebuggerRequestMessage;
typedef JSONMessage<MessageType::DebuggerResponse> DebuggerResponseMessage;
struct AddBreakpointMessage : public Message {
js::BreakpointPosition mPosition;
explicit AddBreakpointMessage(const js::BreakpointPosition& aPosition)
: Message(MessageType::AddBreakpoint, sizeof(*this)),
mPosition(aPosition) {}
};
typedef EmptyMessage<MessageType::ClearBreakpoints> ClearBreakpointsMessage;
struct ResumeMessage : public Message {
// Whether to travel forwards or backwards.
bool mForward;
explicit ResumeMessage(bool aForward)
: Message(MessageType::Resume, sizeof(*this)), mForward(aForward) {}
};
struct RestoreCheckpointMessage : public Message {
// The checkpoint to restore.
size_t mCheckpoint;
explicit RestoreCheckpointMessage(size_t aCheckpoint)
: Message(MessageType::RestoreCheckpoint, sizeof(*this)),
mCheckpoint(aCheckpoint) {}
};
struct RunToPointMessage : public Message {
// The target execution point.
js::ExecutionPoint mTarget;
explicit RunToPointMessage(const js::ExecutionPoint& aTarget)
: Message(MessageType::RunToPoint, sizeof(*this)), mTarget(aTarget) {}
};
struct SetIsActiveMessage : public Message {
// Whether this is the active child process (see ParentIPC.cpp).
bool mActive;
explicit SetIsActiveMessage(bool aActive)
: Message(MessageType::SetIsActive, sizeof(*this)), mActive(aActive) {}
};
struct SetAllowIntentionalCrashesMessage : public Message {
// Whether to allow intentional crashes in the future or not.
bool mAllowed;
explicit SetAllowIntentionalCrashesMessage(bool aAllowed)
: Message(MessageType::SetAllowIntentionalCrashes, sizeof(*this)),
mAllowed(aAllowed) {}
};
struct SetSaveCheckpointMessage : public Message {
// The checkpoint in question.
size_t mCheckpoint;
// Whether to save this checkpoint whenever it is encountered.
bool mSave;
SetSaveCheckpointMessage(size_t aCheckpoint, bool aSave)
: Message(MessageType::SetSaveCheckpoint, sizeof(*this)),
mCheckpoint(aCheckpoint),
mSave(aSave) {}
};
typedef EmptyMessage<MessageType::RecordingFlushed> RecordingFlushedMessage;
typedef JSONMessage<MessageType::ManifestStart> ManifestStartMessage;
typedef JSONMessage<MessageType::ManifestFinished> ManifestFinishedMessage;
struct FatalErrorMessage : public Message {
explicit FatalErrorMessage(uint32_t aSize)
@ -391,29 +272,6 @@ struct PaintMessage : public Message {
mHeight(aHeight) {}
};
struct HitExecutionPointMessage : public Message {
// The point the child paused at.
js::ExecutionPoint mPoint;
// Whether the pause occurred due to hitting the end of the recording.
bool mRecordingEndpoint;
// The amount of non-idle time taken to get to this pause from the last time
// the child paused.
double mDurationMicroseconds;
HitExecutionPointMessage(const js::ExecutionPoint& aPoint,
bool aRecordingEndpoint,
double aDurationMicroseconds)
: Message(MessageType::HitExecutionPoint, sizeof(*this)),
mPoint(aPoint),
mRecordingEndpoint(aRecordingEndpoint),
mDurationMicroseconds(aDurationMicroseconds) {}
};
typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints>
AlwaysMarkMajorCheckpointsMessage;
template <MessageType Type>
struct BinaryMessage : public Message {
explicit BinaryMessage(uint32_t aSize) : Message(Type, aSize) {}

Просмотреть файл

@ -77,9 +77,11 @@ static void ChannelMessageHandler(Message::UniquePtr aMsg) {
switch (aMsg->mType) {
case MessageType::Introduction: {
MonitorAutoLock lock(*gMonitor);
MOZ_RELEASE_ASSERT(!gIntroductionMessage);
gIntroductionMessage.reset(
static_cast<IntroductionMessage*>(aMsg.release()));
gMonitor->NotifyAll();
break;
}
case MessageType::CreateCheckpoint: {
@ -87,7 +89,7 @@ static void ChannelMessageHandler(Message::UniquePtr aMsg) {
// Ignore requests to create checkpoints before we have reached the first
// paint and finished initializing.
if (navigation::IsInitialized()) {
if (js::IsInitialized()) {
uint8_t data = 0;
DirectWrite(gCheckpointWriteFd, &data, 1);
}
@ -113,66 +115,14 @@ static void ChannelMessageHandler(Message::UniquePtr aMsg) {
}
break;
}
case MessageType::SetIsActive: {
const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&)*aMsg;
PauseMainThreadAndInvokeCallback(
[=]() { SetIsActiveChild(nmsg.mActive); });
break;
}
case MessageType::SetAllowIntentionalCrashes: {
const SetAllowIntentionalCrashesMessage& nmsg =
(const SetAllowIntentionalCrashesMessage&)*aMsg;
PauseMainThreadAndInvokeCallback(
[=]() { SetAllowIntentionalCrashes(nmsg.mAllowed); });
break;
}
case MessageType::SetSaveCheckpoint: {
const SetSaveCheckpointMessage& nmsg =
(const SetSaveCheckpointMessage&)*aMsg;
PauseMainThreadAndInvokeCallback(
[=]() { SetSaveCheckpoint(nmsg.mCheckpoint, nmsg.mSave); });
break;
}
case MessageType::FlushRecording: {
PauseMainThreadAndInvokeCallback(FlushRecording);
break;
}
case MessageType::DebuggerRequest: {
const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&)*aMsg;
case MessageType::ManifestStart: {
const ManifestStartMessage& nmsg = (const ManifestStartMessage&)*aMsg;
js::CharBuffer* buf = new js::CharBuffer();
buf->append(nmsg.Buffer(), nmsg.BufferSize());
PauseMainThreadAndInvokeCallback(
[=]() { navigation::DebuggerRequest(buf); });
break;
}
case MessageType::AddBreakpoint: {
const AddBreakpointMessage& nmsg = (const AddBreakpointMessage&)*aMsg;
PauseMainThreadAndInvokeCallback(
[=]() { navigation::AddBreakpoint(nmsg.mPosition); });
break;
}
case MessageType::ClearBreakpoints: {
PauseMainThreadAndInvokeCallback(
[=]() { navigation::ClearBreakpoints(); });
break;
}
case MessageType::Resume: {
const ResumeMessage& nmsg = (const ResumeMessage&)*aMsg;
PauseMainThreadAndInvokeCallback(
[=]() { navigation::Resume(nmsg.mForward); });
break;
}
case MessageType::RestoreCheckpoint: {
const RestoreCheckpointMessage& nmsg =
(const RestoreCheckpointMessage&)*aMsg;
PauseMainThreadAndInvokeCallback(
[=]() { navigation::RestoreCheckpoint(nmsg.mCheckpoint); });
break;
}
case MessageType::RunToPoint: {
const RunToPointMessage& nmsg = (const RunToPointMessage&)*aMsg;
PauseMainThreadAndInvokeCallback(
[=]() { navigation::RunToPoint(nmsg.mTarget); });
PauseMainThreadAndInvokeCallback([=]() {
js::ManifestStart(*buf);
delete buf;
});
break;
}
case MessageType::MiddlemanCallResponse: {
@ -201,8 +151,7 @@ static void ListenForCheckpointThreadMain(void*) {
ssize_t rv = HANDLE_EINTR(read(gCheckpointReadFd, &data, 1));
if (rv > 0) {
NS_DispatchToMainThread(NewRunnableFunction("NewCheckpoint",
NewCheckpoint,
/* aTemporary = */ false));
NewCheckpoint));
} else {
MOZ_RELEASE_ASSERT(errno == EIO);
MOZ_RELEASE_ASSERT(HasDivergedFromRecording());
@ -292,16 +241,20 @@ void InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv) {
pt.reset();
// We are ready to receive initialization messages from the middleman, pause
// so they can be sent.
HitExecutionPoint(js::ExecutionPoint(), /* aRecordingEndpoint = */ false);
// If we failed to initialize then report it to the user.
if (gInitializationFailureMessage) {
ReportFatalError(Nothing(), "%s", gInitializationFailureMessage);
Unreachable();
}
// Wait for the parent to send us the introduction message.
{
MonitorAutoLock lock(*gMonitor);
while (!gIntroductionMessage) {
gMonitor->Wait();
}
}
// Process the introduction message to fill in arguments.
MOZ_RELEASE_ASSERT(gParentArgv.empty());
@ -344,7 +297,7 @@ bool DebuggerRunsInMiddleman() {
void CreateCheckpoint() {
if (!HasDivergedFromRecording()) {
NewCheckpoint(/* aTemporary = */ false);
NewCheckpoint();
}
}
@ -392,16 +345,6 @@ void ReportFatalError(const Maybe<MinidumpInfo>& aMinidump, const char* aFormat,
Thread::WaitForeverNoIdle();
}
void NotifyFlushedRecording() {
gChannel->SendMessage(RecordingFlushedMessage());
}
void NotifyAlwaysMarkMajorCheckpoints() {
if (IsActiveChild()) {
gChannel->SendMessage(AlwaysMarkMajorCheckpointsMessage());
}
}
///////////////////////////////////////////////////////////////////////////////
// Vsyncs
///////////////////////////////////////////////////////////////////////////////
@ -543,10 +486,9 @@ static void PaintFromMainThread() {
// operating on the draw target buffer.
MOZ_RELEASE_ASSERT(!gNumPendingPaints);
if (IsActiveChild() && navigation::ShouldSendPaintMessage() &&
gDrawTargetBuffer) {
if (IsMainChild() && gDrawTargetBuffer) {
memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize);
gChannel->SendMessage(PaintMessage(navigation::LastNormalCheckpoint(),
gChannel->SendMessage(PaintMessage(GetLastCheckpoint(),
gPaintWidth, gPaintHeight));
}
}
@ -630,55 +572,18 @@ bool CurrentRepaintCannotFail() {
return gRepainting && !gAllowRepaintFailures;
}
///////////////////////////////////////////////////////////////////////////////
// Checkpoint Messages
///////////////////////////////////////////////////////////////////////////////
// The time when the last HitExecutionPoint message was sent.
static double gLastPauseTime;
// When recording and we are idle, the time when we became idle.
static double gIdleTimeStart;
void BeginIdleTime() {
MOZ_RELEASE_ASSERT(IsRecording() && NS_IsMainThread() && !gIdleTimeStart);
gIdleTimeStart = CurrentTime();
}
void EndIdleTime() {
MOZ_RELEASE_ASSERT(IsRecording() && NS_IsMainThread() && gIdleTimeStart);
// Erase the idle time from our measurements by advancing the last checkpoint
// time.
gLastPauseTime += CurrentTime() - gIdleTimeStart;
gIdleTimeStart = 0;
}
void HitExecutionPoint(const js::ExecutionPoint& aPoint,
bool aRecordingEndpoint) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
double time = CurrentTime();
PauseMainThreadAndInvokeCallback([=]() {
double duration = 0;
if (gLastPauseTime) {
duration = time - gLastPauseTime;
MOZ_RELEASE_ASSERT(duration > 0);
}
gChannel->SendMessage(
HitExecutionPointMessage(aPoint, aRecordingEndpoint, duration));
});
gLastPauseTime = time;
}
///////////////////////////////////////////////////////////////////////////////
// Message Helpers
///////////////////////////////////////////////////////////////////////////////
void RespondToRequest(const js::CharBuffer& aBuffer) {
DebuggerResponseMessage* msg =
DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length());
gChannel->SendMessage(std::move(*msg));
free(msg);
void ManifestFinished(const js::CharBuffer& aBuffer) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
ManifestFinishedMessage* msg =
ManifestFinishedMessage::New(aBuffer.begin(), aBuffer.length());
PauseMainThreadAndInvokeCallback([=]() {
gChannel->SendMessage(std::move(*msg));
free(msg);
});
}
void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,

Просмотреть файл

@ -13,86 +13,13 @@
#include "MiddlemanCall.h"
#include "Monitor.h"
namespace mozilla {
namespace recordreplay {
// This file has internal definitions for communication between the main
// record/replay infrastructure and child side IPC code.
// The navigation namespace has definitions for managing breakpoints and all
// other state that persists across rewinds, and for keeping track of the
// precise execution position of the child process. The middleman will send the
// child process Resume messages to travel forward and backward, but it is up
// to the child process to keep track of the rewinding and resuming necessary
// to find the next or previous point where a breakpoint or checkpoint is hit.
namespace navigation {
// Navigation state is initialized when the first checkpoint is reached.
bool IsInitialized();
// In a recording process, get the current execution point, aka the endpoint
// of the recording.
js::ExecutionPoint GetRecordingEndpoint();
// In a replaying process, set the recording endpoint. |index| is used to
// differentiate different endpoints that have been sequentially written to
// the recording file as it has been flushed.
void SetRecordingEndpoint(size_t aIndex, const js::ExecutionPoint& aEndpoint);
// Save temporary checkpoints at all opportunities during navigation.
void AlwaysSaveTemporaryCheckpoints();
// Process incoming requests from the middleman.
void DebuggerRequest(js::CharBuffer* aBuffer);
void AddBreakpoint(const js::BreakpointPosition& aPosition);
void ClearBreakpoints();
void Resume(bool aForward);
void RestoreCheckpoint(size_t aId);
void RunToPoint(const js::ExecutionPoint& aPoint);
// Attempt to diverge from the recording so that new recorded events cause
// the process to rewind. Returns false if the divergence failed: either we
// can't rewind, or already diverged here and then had an unhandled divergence.
bool MaybeDivergeFromRecording();
// Notify navigation that a position was hit.
void PositionHit(const js::BreakpointPosition& aPosition);
// Get an execution point for hitting the specified position right now.
js::ExecutionPoint CurrentExecutionPoint(
const Maybe<js::BreakpointPosition>& aPosition);
// Convert an identifier from NewTimeWarpTarget() which we have seen while
// executing into an ExecutionPoint.
js::ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget);
// Synchronously paint the current contents into the graphics shared memory
// object, returning the size of the painted area via aWidth/aHeight.
void Repaint(size_t* aWidth, size_t* aHeight);
// Called when running forward, immediately before hitting a normal or
// temporary checkpoint.
void BeforeCheckpoint();
// Called immediately after hitting a normal or temporary checkpoint, either
// when running forward or immediately after rewinding.
void AfterCheckpoint(const CheckpointId& aCheckpoint);
// Get the ID of the last normal checkpoint.
size_t LastNormalCheckpoint();
// Whether to send a paint message for the last normal checkpoint reached.
bool ShouldSendPaintMessage();
} // namespace navigation
namespace mozilla {
namespace recordreplay {
namespace child {
// IPC activity that can be triggered by navigation.
void RespondToRequest(const js::CharBuffer& aBuffer);
void HitExecutionPoint(const js::ExecutionPoint& aPoint,
bool aRecordingEndpoint);
// Optional information about a crash that occurred. If not provided to
// ReportFatalError, the current thread will be treated as crashed.
struct MinidumpInfo {
@ -115,19 +42,12 @@ void ReportFatalError(const Maybe<MinidumpInfo>& aMinidumpInfo,
// Monitor used for various synchronization tasks.
extern Monitor* gMonitor;
// Notify the middleman that the recording was flushed.
void NotifyFlushedRecording();
// Notify the middleman about an AlwaysMarkMajorCheckpoints directive.
void NotifyAlwaysMarkMajorCheckpoints();
// Mark a time span when the main thread is idle.
void BeginIdleTime();
void EndIdleTime();
// Whether the middleman runs developer tools server code.
bool DebuggerRunsInMiddleman();
// Notify the middleman that the last manifest was finished.
void ManifestFinished(const js::CharBuffer& aResponse);
// Send messages operating on middleman calls.
void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
InfallibleVector<char>* aOutputData);
@ -138,7 +58,6 @@ void SendResetMiddlemanCalls();
bool CurrentRepaintCannotFail();
} // namespace child
} // namespace recordreplay
} // namespace mozilla

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -53,8 +53,7 @@ ChildProcessInfo::~ChildProcessInfo() {
}
}
void ChildProcessInfo::OnIncomingMessage(const Message& aMsg,
bool aForwardToControl) {
void ChildProcessInfo::OnIncomingMessage(const Message& aMsg) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
mLastMessageTime = TimeStamp::Now();
@ -69,27 +68,12 @@ void ChildProcessInfo::OnIncomingMessage(const Message& aMsg,
OnCrash(nmsg.Error());
return;
}
case MessageType::HitExecutionPoint: {
const HitExecutionPointMessage& nmsg =
static_cast<const HitExecutionPointMessage&>(aMsg);
mPaused = true;
if (this == GetActiveChild() && !nmsg.mPoint.HasPosition()) {
MaybeUpdateGraphicsAtCheckpoint(nmsg.mPoint.mCheckpoint);
}
if (aForwardToControl) {
js::ForwardHitExecutionPointMessage(GetId(), nmsg);
}
break;
}
case MessageType::Paint:
MaybeUpdateGraphicsAtPaint(static_cast<const PaintMessage&>(aMsg));
UpdateGraphicsInUIProcess(&static_cast<const PaintMessage&>(aMsg));
break;
case MessageType::DebuggerResponse:
mPaused = true;
js::OnDebuggerResponse(aMsg);
break;
case MessageType::RecordingFlushed:
case MessageType::ManifestFinished:
mPaused = true;
js::ForwardManifestFinished(this, aMsg);
break;
case MessageType::MiddlemanCallRequest: {
const MiddlemanCallRequestMessage& nmsg =
@ -115,16 +99,8 @@ void ChildProcessInfo::SendMessage(Message&& aMsg) {
// Update paused state.
MOZ_RELEASE_ASSERT(IsPaused() || aMsg.CanBeSentWhileUnpaused());
switch (aMsg.mType) {
case MessageType::Resume:
case MessageType::RestoreCheckpoint:
case MessageType::RunToPoint:
case MessageType::DebuggerRequest:
case MessageType::FlushRecording:
mPaused = false;
break;
default:
break;
if (aMsg.mType == MessageType::ManifestStart) {
mPaused = false;
}
mLastMessageTime = TimeStamp::Now();
@ -203,20 +179,8 @@ void ChildProcessInfo::LaunchSubprocess(
SendGraphicsMemoryToChild();
// The child should send us a HitExecutionPoint message with an invalid point
// to pause.
WaitUntilPaused();
MOZ_RELEASE_ASSERT(gIntroductionMessage);
SendMessage(std::move(*gIntroductionMessage));
// Always save the first checkpoint in replaying child processes.
if (!IsRecording()) {
SendMessage(SetSaveCheckpointMessage(CheckpointId::First, true));
}
// Always run forward to the first checkpoint after the primordial one.
SendMessage(ResumeMessage(/* aForward = */ true));
}
void ChildProcessInfo::OnCrash(const char* aWhy) {
@ -291,11 +255,11 @@ static bool gHasPendingMessageRunnable;
// considering that child to be hung.
static const size_t HangSeconds = 30;
Message::UniquePtr ChildProcessInfo::WaitUntilPaused() {
void ChildProcessInfo::WaitUntilPaused() {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (IsPaused()) {
return nullptr;
return;
}
bool sentTerminateMessage = false;
@ -307,9 +271,9 @@ Message::UniquePtr ChildProcessInfo::WaitUntilPaused() {
Message::UniquePtr msg = ExtractChildMessage(&process);
if (msg) {
OnIncomingMessage(*msg, /* aForwardToControl = */ false);
OnIncomingMessage(*msg);
if (IsPaused()) {
return msg;
return;
}
} else {
if (gChildrenAreDebugging || IsRecording()) {
@ -357,7 +321,7 @@ void ChildProcessInfo::MaybeProcessPendingMessageRunnable() {
if (msg) {
MonitorAutoUnlock unlock(*gMonitor);
process->OnIncomingMessage(*msg, /* aForwardToControl = */ true);
process->OnIncomingMessage(*msg);
} else {
break;
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -19,7 +19,10 @@ namespace mozilla {
namespace recordreplay {
struct Message;
struct HitExecutionPointMessage;
namespace parent {
class ChildProcessInfo;
}
namespace js {
@ -35,162 +38,6 @@ namespace js {
// The RecordReplayControl object is also provided here, but has a different
// interface which allows the JS to query the current process.
// Identification for a position where a breakpoint can be installed in a child
// process. Breakpoint positions describe all places between checkpoints where
// the child process can pause and be inspected by the middleman. A particular
// BreakpointPosition can be reached any number of times during execution of
// the process.
struct BreakpointPosition {
// clang-format off
MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(Kind, (
Invalid,
// Break at a script offset. Requires script/offset.
Break,
// Break for an on-step handler within a frame.
// Requires script/offset/frameIndex.
OnStep,
// Break either when any frame is popped, or when a specific frame is
// popped. Requires script/frameIndex in the latter case.
OnPop,
// Break when entering any frame.
EnterFrame,
// Break when a new top-level script is created.
NewScript,
// Break when a message is logged to the web console.
ConsoleMessage,
// Break when NewTimeWarpTarget() is called.
WarpTarget
));
// clang-format on
Kind mKind;
// Optional information associated with the breakpoint.
uint32_t mScript;
uint32_t mOffset;
uint32_t mFrameIndex;
static const uint32_t EMPTY_SCRIPT = (uint32_t)-1;
static const uint32_t EMPTY_OFFSET = (uint32_t)-1;
static const uint32_t EMPTY_FRAME_INDEX = (uint32_t)-1;
BreakpointPosition()
: mKind(Invalid),
mScript(EMPTY_SCRIPT),
mOffset(EMPTY_OFFSET),
mFrameIndex(EMPTY_FRAME_INDEX) {}
explicit BreakpointPosition(Kind aKind, uint32_t aScript = EMPTY_SCRIPT,
uint32_t aOffset = EMPTY_OFFSET,
uint32_t aFrameIndex = EMPTY_FRAME_INDEX)
: mKind(aKind),
mScript(aScript),
mOffset(aOffset),
mFrameIndex(aFrameIndex) {}
bool IsValid() const { return mKind != Invalid; }
inline bool operator==(const BreakpointPosition& o) const {
return mKind == o.mKind && mScript == o.mScript && mOffset == o.mOffset &&
mFrameIndex == o.mFrameIndex;
}
inline bool operator!=(const BreakpointPosition& o) const {
return !(*this == o);
}
// Return whether an execution point matching |o| also matches this.
inline bool Subsumes(const BreakpointPosition& o) const {
return (*this == o) ||
(mKind == OnPop && o.mKind == OnPop && mScript == EMPTY_SCRIPT) ||
(mKind == Break && o.mKind == OnStep && mScript == o.mScript &&
mOffset == o.mOffset);
}
static const char* StaticKindString(Kind aKind) {
switch (aKind) {
case Invalid:
return "Invalid";
case Break:
return "Break";
case OnStep:
return "OnStep";
case OnPop:
return "OnPop";
case EnterFrame:
return "EnterFrame";
case NewScript:
return "NewScript";
case ConsoleMessage:
return "ConsoleMessage";
case WarpTarget:
return "WarpTarget";
}
MOZ_CRASH("Bad BreakpointPosition kind");
}
const char* KindString() const { return StaticKindString(mKind); }
JSObject* Encode(JSContext* aCx) const;
bool Decode(JSContext* aCx, JS::HandleObject aObject);
void ToString(nsCString& aStr) const;
};
// Identification for a point in the execution of a child process where it may
// pause and be inspected by the middleman. A particular execution point will
// be reached exactly once during the execution of the process.
struct ExecutionPoint {
// ID of the last normal checkpoint prior to this point.
size_t mCheckpoint;
// How much progress execution has made prior to reaching the point.
// A given BreakpointPosition may not be reached twice without an intervening
// increment of the global progress counter.
ProgressCounter mProgress;
// The position reached after making the specified amount of progress,
// invalid if the execution point refers to the checkpoint itself.
BreakpointPosition mPosition;
ExecutionPoint() : mCheckpoint(CheckpointId::Invalid), mProgress(0) {}
ExecutionPoint(size_t aCheckpoint, ProgressCounter aProgress)
: mCheckpoint(aCheckpoint), mProgress(aProgress) {}
ExecutionPoint(size_t aCheckpoint, ProgressCounter aProgress,
const BreakpointPosition& aPosition)
: mCheckpoint(aCheckpoint), mProgress(aProgress), mPosition(aPosition) {
// ExecutionPoint positions must be as precise as possible, and cannot
// subsume other positions.
MOZ_RELEASE_ASSERT(aPosition.IsValid());
MOZ_RELEASE_ASSERT(aPosition.mKind != BreakpointPosition::OnPop ||
aPosition.mScript != BreakpointPosition::EMPTY_SCRIPT);
MOZ_RELEASE_ASSERT(aPosition.mKind != BreakpointPosition::Break);
}
bool HasPosition() const { return mPosition.IsValid(); }
inline bool operator==(const ExecutionPoint& o) const {
return mCheckpoint == o.mCheckpoint && mProgress == o.mProgress &&
mPosition == o.mPosition;
}
inline bool operator!=(const ExecutionPoint& o) const {
return !(*this == o);
}
JSObject* Encode(JSContext* aCx) const;
bool Decode(JSContext* aCx, JS::HandleObject aObject);
void ToString(nsCString& aStr) const;
};
// Buffer type used for encoding object data.
typedef InfallibleVector<char16_t> CharBuffer;
@ -198,6 +45,12 @@ typedef InfallibleVector<char16_t> CharBuffer;
// its target script.
void SetupDevtoolsSandbox();
// JS state is initialized when the first checkpoint is reached.
bool IsInitialized();
// Start processing a manifest received from the middleman.
void ManifestStart(const CharBuffer& aContents);
// The following hooks are used in the middleman process to call methods defined
// by the middleman control logic.
@ -205,8 +58,8 @@ void SetupDevtoolsSandbox();
void SetupMiddlemanControl(const Maybe<size_t>& aRecordingChildId);
// Handle an incoming message from a child process.
void ForwardHitExecutionPointMessage(size_t aId,
const HitExecutionPointMessage& aMsg);
void ForwardManifestFinished(parent::ChildProcessInfo* aChild,
const Message& aMsg);
// Prepare the child processes so that the recording file can be safely copied.
void BeforeSaveRecording();
@ -215,27 +68,20 @@ void AfterSaveRecording();
// The following hooks are used in the recording/replaying process to
// call methods defined by the JS sandbox.
// Handle an incoming request from the middleman.
void ProcessRequest(const char16_t* aRequest, size_t aRequestLength,
CharBuffer* aResponse);
// Called when running forward, immediately before hitting a normal or
// temporary checkpoint.
void BeforeCheckpoint();
// Ensure there is a handler in place that will call
// RecordReplayControl.positionHit whenever the specified execution position is
// reached.
void EnsurePositionHandler(const BreakpointPosition& aPosition);
// Called immediately after hitting a normal or temporary checkpoint, either
// when running forward or immediately after rewinding. aRestoredCheckpoint is
// true if we just rewound.
void AfterCheckpoint(size_t aCheckpoint, bool aRestoredCheckpoint);
// Clear all installed position handlers.
void ClearPositionHandlers();
// Accessors for state which can be accessed from JS.
// Clear all state that is kept while execution is paused.
void ClearPausedState();
// Given an execution position inside a script, get an execution position for
// the entry point of that script, otherwise return nothing.
Maybe<BreakpointPosition> GetEntryPosition(const BreakpointPosition& aPosition);
// Called after receiving a DebuggerResponse from a child.
void OnDebuggerResponse(const Message& aMsg);
// Mark a time span when the main thread is idle.
void BeginIdleTime();
void EndIdleTime();
} // namespace js
} // namespace recordreplay

Просмотреть файл

@ -275,8 +275,7 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol {
if (mSide == ipc::ChildSide) {
AutoMarkMainThreadWaitingForIPDLReply blocked;
while (!aReply) {
GetActiveChild()->WaitUntilPaused();
GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true));
MOZ_CRASH("NYI");
}
} else {
MonitorAutoLock lock(*gMonitor);
@ -319,8 +318,7 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol {
if (mSide == ipc::ChildSide) {
AutoMarkMainThreadWaitingForIPDLReply blocked;
while (!aReply) {
GetActiveChild()->WaitUntilPaused();
GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true));
MOZ_CRASH("NYI");
}
} else {
MonitorAutoLock lock(*gMonitor);

Просмотреть файл

@ -104,14 +104,6 @@ static size_t gLastPaintWidth, gLastPaintHeight;
// its graphics, we wait until both the Paint and the checkpoint itself have
// been hit, with no intervening repaint.
// The last explicit paint message received from the child, if there has not
// been an intervening repaint.
static UniquePtr<PaintMessage> gLastExplicitPaint;
// The last checkpoint the child reached, if there has not been an intervening
// repaint.
static size_t gLastCheckpoint;
void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
@ -126,10 +118,6 @@ void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) {
bool hadFailure = !aMsg;
// Clear out the last explicit paint information. This may delete aMsg.
gLastExplicitPaint = nullptr;
gLastCheckpoint = CheckpointId::Invalid;
// Make sure there is a sandbox which is running the graphics JS module.
if (!gGraphics) {
InitGraphicsSandbox();
@ -187,23 +175,6 @@ void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) {
MOZ_ALWAYS_TRUE(JS::DetachArrayBuffer(cx, bufferObject));
}
static void MaybeTriggerExplicitPaint() {
if (gLastExplicitPaint &&
gLastExplicitPaint->mCheckpointId == gLastCheckpoint) {
UpdateGraphicsInUIProcess(gLastExplicitPaint.get());
}
}
void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg) {
gLastExplicitPaint.reset(new PaintMessage(aMsg));
MaybeTriggerExplicitPaint();
}
void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId) {
gLastCheckpoint = aCheckpointId;
MaybeTriggerExplicitPaint();
}
bool InRepaintStressMode() {
static bool checked = false;
static bool rv;

Просмотреть файл

@ -89,18 +89,6 @@ size_t SpawnReplayingChild() {
return child->GetId();
}
void SetActiveChild(ChildProcessInfo* aChild) {
MOZ_RELEASE_ASSERT(aChild->IsPaused());
if (gActiveChild) {
MOZ_RELEASE_ASSERT(gActiveChild->IsPaused());
gActiveChild->SendMessage(SetIsActiveMessage(false));
}
aChild->SendMessage(SetIsActiveMessage(true));
gActiveChild = aChild;
}
void ResumeBeforeWaitingForIPDLReply() {
MOZ_RELEASE_ASSERT(gActiveChild->IsRecording());
@ -108,7 +96,7 @@ void ResumeBeforeWaitingForIPDLReply() {
// recording child process. If the child is paused, resume it immediately so
// that we don't deadlock.
if (gActiveChild->IsPaused()) {
gActiveChild->SendMessage(ResumeMessage(/* aForward = */ true));
MOZ_CRASH("NYI");
}
}

Просмотреть файл

@ -76,11 +76,6 @@ void SendGraphicsMemoryToChild();
// an unhandled recording divergence.
void UpdateGraphicsInUIProcess(const PaintMessage* aMsg);
// If necessary, update graphics after the active child sends a paint message
// or reaches a checkpoint.
void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg);
void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId);
// ID for the mach message sent from a child process to the middleman to
// request a port for the graphics shmem.
static const int32_t GraphicsHandshakeMessageId = 42;
@ -141,7 +136,7 @@ class ChildProcessInfo {
bool mHasBegunFatalError;
bool mHasFatalError;
void OnIncomingMessage(const Message& aMsg, bool aForwardToControl);
void OnIncomingMessage(const Message& aMsg);
static void MaybeProcessPendingMessageRunnable();
void ReceiveChildMessageOnMainThread(Message::UniquePtr aMsg);
@ -164,9 +159,8 @@ class ChildProcessInfo {
// Handle incoming messages from this process (and no others) until it pauses.
// The return value is null if it is already paused, otherwise the message
// which caused it to pause. In the latter case, OnIncomingMessage will *not*
// be called with the message.
Message::UniquePtr WaitUntilPaused();
// which caused it to pause.
void WaitUntilPaused();
static void SetIntroductionMessage(IntroductionMessage* aMessage);
};

Просмотреть файл

@ -18,7 +18,6 @@ if CONFIG['OS_ARCH'] == 'Darwin' and CONFIG['NIGHTLY_BUILD']:
'HashTable.cpp',
'ipc/Channel.cpp',
'ipc/ChildIPC.cpp',
'ipc/ChildNavigation.cpp',
'ipc/ChildProcess.cpp',
'ipc/JSControl.cpp',
'ipc/ParentForwarding.cpp',