mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-04 11:26:09 +00:00
14486004b6
# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D18488 --HG-- extra : moz-landing-system : lando
459 lines
15 KiB
C++
459 lines
15 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "MiddlemanCall.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
namespace mozilla {
|
|
namespace recordreplay {
|
|
|
|
typedef std::unordered_map<const void*, MiddlemanCall*> MiddlemanCallMap;
|
|
|
|
// State used for keeping track of middleman calls in either a replaying
|
|
// process or middleman process.
|
|
struct MiddlemanCallState {
|
|
// In a replaying or middleman process, all middleman calls that have been
|
|
// encountered, indexed by their ID.
|
|
InfallibleVector<MiddlemanCall*> mCalls;
|
|
|
|
// In a replaying or middleman process, association between values produced by
|
|
// a middleman call and the call itself.
|
|
MiddlemanCallMap mCallMap;
|
|
|
|
// In a middleman process, any buffers allocated for performed calls.
|
|
InfallibleVector<void*> mAllocatedBuffers;
|
|
};
|
|
|
|
// In a replaying process, all middleman call state. In a middleman process,
|
|
// state for the child currently being processed.
|
|
static MiddlemanCallState* gState;
|
|
|
|
// In a middleman process, middleman call state for each child process, indexed
|
|
// by the child ID.
|
|
static StaticInfallibleVector<MiddlemanCallState*> gStatePerChild;
|
|
|
|
// In a replaying process, lock protecting middleman call state. In the
|
|
// middleman, all accesses occur on the main thread.
|
|
static Monitor* gMonitor;
|
|
|
|
void InitializeMiddlemanCalls() {
|
|
MOZ_RELEASE_ASSERT(IsRecordingOrReplaying() || IsMiddleman());
|
|
|
|
if (IsReplaying()) {
|
|
gState = new MiddlemanCallState();
|
|
gMonitor = new Monitor();
|
|
}
|
|
}
|
|
|
|
// Apply the ReplayInput phase to aCall and any calls it depends on that have
|
|
// not been sent to the middleman yet, filling aOutgoingCalls with the set of
|
|
// such calls.
|
|
static bool GatherDependentCalls(
|
|
InfallibleVector<MiddlemanCall*>& aOutgoingCalls, MiddlemanCall* aCall) {
|
|
MOZ_RELEASE_ASSERT(!aCall->mSent);
|
|
aCall->mSent = true;
|
|
|
|
const Redirection& redirection = GetRedirection(aCall->mCallId);
|
|
|
|
CallArguments arguments;
|
|
aCall->mArguments.CopyTo(&arguments);
|
|
|
|
InfallibleVector<MiddlemanCall*> dependentCalls;
|
|
|
|
MiddlemanCallContext cx(aCall, &arguments, MiddlemanCallPhase::ReplayInput);
|
|
cx.mDependentCalls = &dependentCalls;
|
|
redirection.mMiddlemanCall(cx);
|
|
if (cx.mFailed) {
|
|
if (child::CurrentRepaintCannotFail()) {
|
|
child::ReportFatalError(Nothing(), "Middleman call input failed: %s\n",
|
|
redirection.mName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (MiddlemanCall* dependent : dependentCalls) {
|
|
if (!dependent->mSent && !GatherDependentCalls(aOutgoingCalls, dependent)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
aOutgoingCalls.append(aCall);
|
|
return true;
|
|
}
|
|
|
|
bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments,
|
|
bool aDiverged) {
|
|
MOZ_RELEASE_ASSERT(IsReplaying());
|
|
|
|
const Redirection& redirection = GetRedirection(aCallId);
|
|
MOZ_RELEASE_ASSERT(redirection.mMiddlemanCall);
|
|
|
|
MonitorAutoLock lock(*gMonitor);
|
|
|
|
// Allocate and fill in a new MiddlemanCall.
|
|
size_t id = gState->mCalls.length();
|
|
MiddlemanCall* newCall = new MiddlemanCall();
|
|
gState->mCalls.emplaceBack(newCall);
|
|
newCall->mId = id;
|
|
newCall->mCallId = aCallId;
|
|
newCall->mArguments.CopyFrom(aArguments);
|
|
|
|
// Perform the ReplayPreface phase on the new call.
|
|
{
|
|
MiddlemanCallContext cx(newCall, aArguments,
|
|
MiddlemanCallPhase::ReplayPreface);
|
|
redirection.mMiddlemanCall(cx);
|
|
if (cx.mFailed) {
|
|
delete newCall;
|
|
gState->mCalls.popBack();
|
|
if (child::CurrentRepaintCannotFail()) {
|
|
child::ReportFatalError(Nothing(),
|
|
"Middleman call preface failed: %s\n",
|
|
redirection.mName);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Other phases will not run if we have not diverged from the recording.
|
|
// Any outputs for the call have been handled by the SaveOutput hook.
|
|
if (!aDiverged) {
|
|
return true;
|
|
}
|
|
|
|
// Perform the ReplayInput phase on the new call and any others it depends on.
|
|
InfallibleVector<MiddlemanCall*> outgoingCalls;
|
|
if (!GatherDependentCalls(outgoingCalls, newCall)) {
|
|
for (MiddlemanCall* call : outgoingCalls) {
|
|
call->mSent = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Encode all calls we are sending to the middleman.
|
|
InfallibleVector<char> inputData;
|
|
BufferStream inputStream(&inputData);
|
|
for (MiddlemanCall* call : outgoingCalls) {
|
|
call->EncodeInput(inputStream);
|
|
}
|
|
|
|
// Perform the calls synchronously in the middleman.
|
|
InfallibleVector<char> outputData;
|
|
child::SendMiddlemanCallRequest(inputData.begin(), inputData.length(),
|
|
&outputData);
|
|
|
|
// Decode outputs for the calls just sent, and perform the ReplayOutput phase
|
|
// on any older dependent calls we sent.
|
|
BufferStream outputStream(outputData.begin(), outputData.length());
|
|
for (MiddlemanCall* call : outgoingCalls) {
|
|
call->DecodeOutput(outputStream);
|
|
|
|
if (call != newCall) {
|
|
CallArguments oldArguments;
|
|
call->mArguments.CopyTo(&oldArguments);
|
|
MiddlemanCallContext cx(call, &oldArguments,
|
|
MiddlemanCallPhase::ReplayOutput);
|
|
cx.mReplayOutputIsOld = true;
|
|
GetRedirection(call->mCallId).mMiddlemanCall(cx);
|
|
}
|
|
}
|
|
|
|
// Perform the ReplayOutput phase to fill in outputs for the current call.
|
|
newCall->mArguments.CopyTo(aArguments);
|
|
MiddlemanCallContext cx(newCall, aArguments,
|
|
MiddlemanCallPhase::ReplayOutput);
|
|
redirection.mMiddlemanCall(cx);
|
|
return true;
|
|
}
|
|
|
|
void ProcessMiddlemanCall(size_t aChildId, const char* aInputData,
|
|
size_t aInputSize,
|
|
InfallibleVector<char>* aOutputData) {
|
|
MOZ_RELEASE_ASSERT(IsMiddleman());
|
|
|
|
while (aChildId >= gStatePerChild.length()) {
|
|
gStatePerChild.append(nullptr);
|
|
}
|
|
if (!gStatePerChild[aChildId]) {
|
|
gStatePerChild[aChildId] = new MiddlemanCallState();
|
|
}
|
|
gState = gStatePerChild[aChildId];
|
|
|
|
BufferStream inputStream(aInputData, aInputSize);
|
|
BufferStream outputStream(aOutputData);
|
|
|
|
while (!inputStream.IsEmpty()) {
|
|
MiddlemanCall* call = new MiddlemanCall();
|
|
call->DecodeInput(inputStream);
|
|
|
|
const Redirection& redirection = GetRedirection(call->mCallId);
|
|
MOZ_RELEASE_ASSERT(redirection.mMiddlemanCall);
|
|
|
|
CallArguments arguments;
|
|
call->mArguments.CopyTo(&arguments);
|
|
|
|
bool skipCall;
|
|
{
|
|
MiddlemanCallContext cx(call, &arguments,
|
|
MiddlemanCallPhase::MiddlemanInput);
|
|
redirection.mMiddlemanCall(cx);
|
|
skipCall = cx.mSkipCallInMiddleman;
|
|
}
|
|
|
|
if (!skipCall) {
|
|
RecordReplayInvokeCall(redirection.mBaseFunction, &arguments);
|
|
}
|
|
|
|
{
|
|
MiddlemanCallContext cx(call, &arguments,
|
|
MiddlemanCallPhase::MiddlemanOutput);
|
|
redirection.mMiddlemanCall(cx);
|
|
}
|
|
|
|
call->mArguments.CopyFrom(&arguments);
|
|
call->EncodeOutput(outputStream);
|
|
|
|
while (call->mId >= gState->mCalls.length()) {
|
|
gState->mCalls.emplaceBack(nullptr);
|
|
}
|
|
MOZ_RELEASE_ASSERT(!gState->mCalls[call->mId]);
|
|
gState->mCalls[call->mId] = call;
|
|
}
|
|
|
|
gState = nullptr;
|
|
}
|
|
|
|
void* MiddlemanCallContext::AllocateBytes(size_t aSize) {
|
|
void* rv = malloc(aSize);
|
|
|
|
// In a middleman process, any buffers we allocate live until the calls are
|
|
// reset. In a replaying process, the buffers will either live forever
|
|
// (if they are allocated in the ReplayPreface phase, to match the lifetime
|
|
// of the MiddlemanCall itself) or will be recovered when we rewind after we
|
|
// are done with our divergence from the recording (any other phase).
|
|
if (IsMiddleman()) {
|
|
gState->mAllocatedBuffers.append(rv);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void ResetMiddlemanCalls(size_t aChildId) {
|
|
MOZ_RELEASE_ASSERT(IsMiddleman());
|
|
|
|
if (aChildId >= gStatePerChild.length()) {
|
|
return;
|
|
}
|
|
|
|
gState = gStatePerChild[aChildId];
|
|
if (!gState) {
|
|
return;
|
|
}
|
|
|
|
for (MiddlemanCall* call : gState->mCalls) {
|
|
if (call) {
|
|
CallArguments arguments;
|
|
call->mArguments.CopyTo(&arguments);
|
|
|
|
MiddlemanCallContext cx(call, &arguments,
|
|
MiddlemanCallPhase::MiddlemanRelease);
|
|
GetRedirection(call->mCallId).mMiddlemanCall(cx);
|
|
}
|
|
}
|
|
|
|
// Delete the calls in a second pass. The MiddlemanRelease phase depends on
|
|
// previous middleman calls still existing.
|
|
for (MiddlemanCall* call : gState->mCalls) {
|
|
delete call;
|
|
}
|
|
|
|
gState->mCalls.clear();
|
|
for (auto buffer : gState->mAllocatedBuffers) {
|
|
free(buffer);
|
|
}
|
|
gState->mAllocatedBuffers.clear();
|
|
gState->mCallMap.clear();
|
|
|
|
gState = nullptr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// System Values
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void AddMiddlemanCallValue(const void* aThing, MiddlemanCall* aCall) {
|
|
gState->mCallMap.erase(aThing);
|
|
gState->mCallMap.insert(MiddlemanCallMap::value_type(aThing, aCall));
|
|
}
|
|
|
|
static MiddlemanCall* LookupMiddlemanCall(const void* aThing) {
|
|
MiddlemanCallMap::const_iterator iter = gState->mCallMap.find(aThing);
|
|
if (iter != gState->mCallMap.end()) {
|
|
return iter->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static const void* GetMiddlemanCallValue(size_t aId) {
|
|
MOZ_RELEASE_ASSERT(IsMiddleman());
|
|
MOZ_RELEASE_ASSERT(aId < gState->mCalls.length() && gState->mCalls[aId] &&
|
|
gState->mCalls[aId]->mMiddlemanValue.isSome());
|
|
return gState->mCalls[aId]->mMiddlemanValue.ref();
|
|
}
|
|
|
|
bool MM_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr) {
|
|
MOZ_RELEASE_ASSERT(aCx.AccessPreface());
|
|
|
|
if (!*aThingPtr) {
|
|
// Null values are handled by the normal argument copying logic.
|
|
return true;
|
|
}
|
|
|
|
Maybe<size_t> callId;
|
|
if (aCx.mPhase == MiddlemanCallPhase::ReplayPreface) {
|
|
// Determine any middleman call this object came from, before the pointer
|
|
// has a chance to be clobbered by another call between this and the
|
|
// ReplayInput phase.
|
|
MiddlemanCall* call = LookupMiddlemanCall(*aThingPtr);
|
|
if (call) {
|
|
callId.emplace(call->mId);
|
|
}
|
|
}
|
|
aCx.ReadOrWritePrefaceBytes(&callId, sizeof(callId));
|
|
|
|
switch (aCx.mPhase) {
|
|
case MiddlemanCallPhase::ReplayPreface:
|
|
return true;
|
|
case MiddlemanCallPhase::ReplayInput:
|
|
if (callId.isSome()) {
|
|
aCx.WriteInputScalar(callId.ref());
|
|
aCx.mDependentCalls->append(gState->mCalls[callId.ref()]);
|
|
return true;
|
|
}
|
|
return false;
|
|
case MiddlemanCallPhase::MiddlemanInput:
|
|
if (callId.isSome()) {
|
|
size_t callIndex = aCx.ReadInputScalar();
|
|
*aThingPtr = GetMiddlemanCallValue(callIndex);
|
|
return true;
|
|
}
|
|
return false;
|
|
default:
|
|
MOZ_CRASH("Bad phase");
|
|
}
|
|
}
|
|
|
|
// Pointer system values are preserved during the replay so that null tests
|
|
// and equality tests work as expected. We additionally mangle the
|
|
// pointers here by setting one of the two highest bits, depending on whether
|
|
// the pointer came from the recording or from the middleman. This avoids
|
|
// accidentally conflating pointers that happen to have the same value but
|
|
// which originate from different processes.
|
|
static const void* MangleSystemValue(const void* aValue, bool aFromRecording) {
|
|
return (const void*)((size_t)aValue | (1ULL << (aFromRecording ? 63 : 62)));
|
|
}
|
|
|
|
void MM_SystemOutput(MiddlemanCallContext& aCx, const void** aOutput,
|
|
bool aUpdating) {
|
|
if (!*aOutput) {
|
|
if (aCx.mPhase == MiddlemanCallPhase::MiddlemanOutput) {
|
|
aCx.mCall->SetMiddlemanValue(*aOutput);
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (aCx.mPhase) {
|
|
case MiddlemanCallPhase::ReplayPreface:
|
|
if (!HasDivergedFromRecording()) {
|
|
// If we haven't diverged from the recording, use the output value saved
|
|
// in the recording.
|
|
if (!aUpdating) {
|
|
*aOutput = MangleSystemValue(*aOutput, true);
|
|
}
|
|
aCx.mCall->SetRecordingValue(*aOutput);
|
|
AddMiddlemanCallValue(*aOutput, aCx.mCall);
|
|
}
|
|
break;
|
|
case MiddlemanCallPhase::MiddlemanOutput:
|
|
aCx.mCall->SetMiddlemanValue(*aOutput);
|
|
AddMiddlemanCallValue(*aOutput, aCx.mCall);
|
|
break;
|
|
case MiddlemanCallPhase::ReplayOutput: {
|
|
if (!aUpdating) {
|
|
*aOutput = MangleSystemValue(*aOutput, false);
|
|
}
|
|
aCx.mCall->SetMiddlemanValue(*aOutput);
|
|
|
|
// Associate the value produced by the middleman with this call. If the
|
|
// call previously went through the ReplayPreface phase when we did not
|
|
// diverge from the recording, we will associate values from both the
|
|
// recording and middleman processes with this call. If a call made after
|
|
// diverging produced the same value as a call made before diverging, use
|
|
// the value saved in the recording for the first call, so that equality
|
|
// tests on the value work as expected.
|
|
MiddlemanCall* previousCall = LookupMiddlemanCall(*aOutput);
|
|
if (previousCall) {
|
|
if (previousCall->mRecordingValue.isSome()) {
|
|
*aOutput = previousCall->mRecordingValue.ref();
|
|
}
|
|
} else {
|
|
AddMiddlemanCallValue(*aOutput, aCx.mCall);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// MiddlemanCall
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void MiddlemanCall::EncodeInput(BufferStream& aStream) const {
|
|
aStream.WriteScalar(mId);
|
|
aStream.WriteScalar(mCallId);
|
|
aStream.WriteBytes(&mArguments, sizeof(CallRegisterArguments));
|
|
aStream.WriteScalar(mPreface.length());
|
|
aStream.WriteBytes(mPreface.begin(), mPreface.length());
|
|
aStream.WriteScalar(mInput.length());
|
|
aStream.WriteBytes(mInput.begin(), mInput.length());
|
|
}
|
|
|
|
void MiddlemanCall::DecodeInput(BufferStream& aStream) {
|
|
mId = aStream.ReadScalar();
|
|
mCallId = aStream.ReadScalar();
|
|
aStream.ReadBytes(&mArguments, sizeof(CallRegisterArguments));
|
|
size_t prefaceLength = aStream.ReadScalar();
|
|
mPreface.appendN(0, prefaceLength);
|
|
aStream.ReadBytes(mPreface.begin(), prefaceLength);
|
|
size_t inputLength = aStream.ReadScalar();
|
|
mInput.appendN(0, inputLength);
|
|
aStream.ReadBytes(mInput.begin(), inputLength);
|
|
}
|
|
|
|
void MiddlemanCall::EncodeOutput(BufferStream& aStream) const {
|
|
aStream.WriteBytes(&mArguments, sizeof(CallRegisterArguments));
|
|
aStream.WriteScalar(mOutput.length());
|
|
aStream.WriteBytes(mOutput.begin(), mOutput.length());
|
|
}
|
|
|
|
void MiddlemanCall::DecodeOutput(BufferStream& aStream) {
|
|
// Only update the return value when decoding arguments, so that we don't
|
|
// clobber the call's arguments with any changes made in the middleman.
|
|
CallRegisterArguments newArguments;
|
|
aStream.ReadBytes(&newArguments, sizeof(CallRegisterArguments));
|
|
mArguments.CopyRvalFrom(&newArguments);
|
|
|
|
size_t outputLength = aStream.ReadScalar();
|
|
mOutput.appendN(0, outputLength);
|
|
aStream.ReadBytes(mOutput.begin(), outputLength);
|
|
}
|
|
|
|
} // namespace recordreplay
|
|
} // namespace mozilla
|