зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1547084 Part 3 - C++ changes and removal for new control logic, r=loganfsmyth.
--HG-- extra : rebase_source : e5c9c1aa48b8657b71527dce273feddc57bd0e3b
This commit is contained in:
Родитель
c201146e83
Коммит
96163d0830
|
@ -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',
|
||||
|
|
Загрузка…
Ссылка в новой задаче