зеркало из https://github.com/mozilla/gecko-dev.git
518 строки
15 KiB
C++
518 строки
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/. */
|
|
|
|
// This file has the logic which the replayed process uses to communicate with
|
|
// the middleman process.
|
|
|
|
#include "ChildInternal.h"
|
|
|
|
#include "base/message_loop.h"
|
|
#include "base/task.h"
|
|
#include "chrome/common/child_thread.h"
|
|
#include "chrome/common/mach_ipc_mac.h"
|
|
#include "ipc/Channel.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/layers/ImageDataSerializer.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "mozilla/VsyncDispatcher.h"
|
|
|
|
#include "InfallibleVector.h"
|
|
#include "MemorySnapshot.h"
|
|
#include "ParentInternal.h"
|
|
#include "ProcessRecordReplay.h"
|
|
#include "ProcessRedirect.h"
|
|
#include "ProcessRewind.h"
|
|
#include "Thread.h"
|
|
#include "Units.h"
|
|
|
|
#include <algorithm>
|
|
#include <mach/mach_vm.h>
|
|
#include <unistd.h>
|
|
|
|
namespace mozilla {
|
|
namespace recordreplay {
|
|
namespace child {
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Record/Replay IPC
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Monitor used for various synchronization tasks.
|
|
Monitor* gMonitor;
|
|
|
|
// The singleton channel for communicating with the middleman.
|
|
Channel* gChannel;
|
|
|
|
static base::ProcessId gMiddlemanPid;
|
|
static base::ProcessId gParentPid;
|
|
static StaticInfallibleVector<char*> gParentArgv;
|
|
|
|
// File descriptors used by a pipe to create checkpoints when instructed by the
|
|
// parent process.
|
|
static FileHandle gCheckpointWriteFd;
|
|
static FileHandle gCheckpointReadFd;
|
|
|
|
// Copy of the introduction message we got from the middleman. This is saved on
|
|
// receipt and then processed during InitRecordingOrReplayingProcess.
|
|
static IntroductionMessage* gIntroductionMessage;
|
|
|
|
// Processing routine for incoming channel messages.
|
|
static void
|
|
ChannelMessageHandler(Message* aMsg)
|
|
{
|
|
MOZ_RELEASE_ASSERT(MainThreadShouldPause() ||
|
|
aMsg->mType == MessageType::CreateCheckpoint ||
|
|
aMsg->mType == MessageType::Terminate);
|
|
|
|
switch (aMsg->mType) {
|
|
case MessageType::Introduction: {
|
|
MOZ_RELEASE_ASSERT(!gIntroductionMessage);
|
|
gIntroductionMessage = (IntroductionMessage*) aMsg->Clone();
|
|
break;
|
|
}
|
|
case MessageType::CreateCheckpoint: {
|
|
MOZ_RELEASE_ASSERT(IsRecording());
|
|
|
|
// Ignore requests to create checkpoints before we have reached the first
|
|
// paint and finished initializing.
|
|
if (navigation::IsInitialized()) {
|
|
uint8_t data = 0;
|
|
DirectWrite(gCheckpointWriteFd, &data, 1);
|
|
}
|
|
break;
|
|
}
|
|
case MessageType::Terminate: {
|
|
PrintSpew("Terminate message received, exiting...\n");
|
|
MOZ_RELEASE_ASSERT(IsRecording());
|
|
_exit(0);
|
|
}
|
|
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;
|
|
js::CharBuffer* buf = new js::CharBuffer();
|
|
buf->append(nmsg.Buffer(), nmsg.BufferSize());
|
|
PauseMainThreadAndInvokeCallback([=]() { navigation::DebuggerRequest(buf); });
|
|
break;
|
|
}
|
|
case MessageType::SetBreakpoint: {
|
|
const SetBreakpointMessage& nmsg = (const SetBreakpointMessage&) *aMsg;
|
|
PauseMainThreadAndInvokeCallback([=]() {
|
|
navigation::SetBreakpoint(nmsg.mId, nmsg.mPosition);
|
|
});
|
|
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);
|
|
});
|
|
break;
|
|
}
|
|
default:
|
|
MOZ_CRASH();
|
|
}
|
|
|
|
free(aMsg);
|
|
}
|
|
|
|
// Main routine for a thread whose sole purpose is to listen to requests from
|
|
// the middleman process to create a new checkpoint. This is separate from the
|
|
// channel thread because this thread is recorded and the latter is not
|
|
// recorded. By communicating between the two threads with a pipe, this
|
|
// thread's behavior will be replicated exactly when replaying and new
|
|
// checkpoints will be created at the same point as during recording.
|
|
static void
|
|
ListenForCheckpointThreadMain(void*)
|
|
{
|
|
while (true) {
|
|
uint8_t data = 0;
|
|
ssize_t rv = read(gCheckpointReadFd, &data, 1);
|
|
if (rv > 0) {
|
|
NS_DispatchToMainThread(NewRunnableFunction("NewCheckpoint", NewCheckpoint,
|
|
/* aTemporary = */ false));
|
|
} else {
|
|
MOZ_RELEASE_ASSERT(errno == EINTR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void* gGraphicsShmem;
|
|
|
|
void
|
|
InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv)
|
|
{
|
|
if (!IsRecordingOrReplaying()) {
|
|
return;
|
|
}
|
|
|
|
Maybe<int> middlemanPid;
|
|
Maybe<int> channelID;
|
|
for (int i = 0; i < *aArgc; i++) {
|
|
if (!strcmp((*aArgv)[i], gMiddlemanPidOption)) {
|
|
MOZ_RELEASE_ASSERT(middlemanPid.isNothing() && i + 1 < *aArgc);
|
|
middlemanPid.emplace(atoi((*aArgv)[i + 1]));
|
|
}
|
|
if (!strcmp((*aArgv)[i], gChannelIDOption)) {
|
|
MOZ_RELEASE_ASSERT(channelID.isNothing() && i + 1 < *aArgc);
|
|
channelID.emplace(atoi((*aArgv)[i + 1]));
|
|
}
|
|
}
|
|
MOZ_RELEASE_ASSERT(middlemanPid.isSome());
|
|
MOZ_RELEASE_ASSERT(channelID.isSome());
|
|
|
|
gMiddlemanPid = middlemanPid.ref();
|
|
|
|
Maybe<AutoPassThroughThreadEvents> pt;
|
|
pt.emplace();
|
|
|
|
gMonitor = new Monitor();
|
|
gChannel = new Channel(channelID.ref(), /* aMiddlemanRecording = */ false, ChannelMessageHandler);
|
|
|
|
pt.reset();
|
|
|
|
DirectCreatePipe(&gCheckpointWriteFd, &gCheckpointReadFd);
|
|
|
|
Thread::StartThread(ListenForCheckpointThreadMain, nullptr, false);
|
|
|
|
pt.emplace();
|
|
|
|
// Setup a mach port to receive the graphics shmem handle over.
|
|
ReceivePort receivePort(nsPrintfCString("WebReplay.%d.%d", gMiddlemanPid, (int) channelID.ref()).get());
|
|
|
|
MachSendMessage handshakeMessage(parent::GraphicsHandshakeMessageId);
|
|
handshakeMessage.AddDescriptor(MachMsgPortDescriptor(receivePort.GetPort(), MACH_MSG_TYPE_COPY_SEND));
|
|
|
|
MachPortSender sender(nsPrintfCString("WebReplay.%d", gMiddlemanPid).get());
|
|
kern_return_t kr = sender.SendMessage(handshakeMessage, 1000);
|
|
MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS);
|
|
|
|
// The parent should send us a handle to the graphics shmem.
|
|
MachReceiveMessage message;
|
|
kr = receivePort.WaitForMessage(&message, 0);
|
|
MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS);
|
|
MOZ_RELEASE_ASSERT(message.GetMessageID() == parent::GraphicsMemoryMessageId);
|
|
mach_port_t graphicsPort = message.GetTranslatedPort(0);
|
|
MOZ_RELEASE_ASSERT(graphicsPort != MACH_PORT_NULL);
|
|
|
|
mach_vm_address_t address = 0;
|
|
kr = mach_vm_map(mach_task_self(), &address, parent::GraphicsMemorySize, 0, VM_FLAGS_ANYWHERE,
|
|
graphicsPort, 0, false,
|
|
VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE,
|
|
VM_INHERIT_NONE);
|
|
MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS);
|
|
|
|
gGraphicsShmem = (void*) address;
|
|
|
|
// The graphics shared memory contents are excluded from snapshots. We do not
|
|
// want checkpoint restores in this child to interfere with drawing being
|
|
// performed by another child.
|
|
AddInitialUntrackedMemoryRegion((uint8_t*) gGraphicsShmem, parent::GraphicsMemorySize);
|
|
|
|
pt.reset();
|
|
|
|
// We are ready to receive initialization messages from the middleman, pause
|
|
// so they can be sent.
|
|
HitCheckpoint(CheckpointId::Invalid, /* aRecordingEndpoint = */ false);
|
|
|
|
// Process the introduction message to fill in arguments.
|
|
MOZ_RELEASE_ASSERT(gParentArgv.empty());
|
|
|
|
gParentPid = gIntroductionMessage->mParentPid;
|
|
|
|
// Record/replay the introduction message itself so we get consistent args
|
|
// between recording and replaying.
|
|
{
|
|
IntroductionMessage* msg = IntroductionMessage::RecordReplay(*gIntroductionMessage);
|
|
|
|
const char* pos = msg->ArgvString();
|
|
for (size_t i = 0; i < msg->mArgc; i++) {
|
|
gParentArgv.append(strdup(pos));
|
|
pos += strlen(pos) + 1;
|
|
}
|
|
|
|
free(msg);
|
|
}
|
|
|
|
free(gIntroductionMessage);
|
|
gIntroductionMessage = nullptr;
|
|
|
|
// Some argument manipulation code expects a null pointer at the end.
|
|
gParentArgv.append(nullptr);
|
|
|
|
MOZ_RELEASE_ASSERT(*aArgc >= 1);
|
|
MOZ_RELEASE_ASSERT(gParentArgv.back() == nullptr);
|
|
|
|
*aArgc = gParentArgv.length() - 1; // For the trailing null.
|
|
*aArgv = gParentArgv.begin();
|
|
|
|
// If we failed to initialize then report it to the user.
|
|
if (gInitializationFailureMessage) {
|
|
ReportFatalError("%s", gInitializationFailureMessage);
|
|
Unreachable();
|
|
}
|
|
}
|
|
|
|
base::ProcessId
|
|
MiddlemanProcessId()
|
|
{
|
|
return gMiddlemanPid;
|
|
}
|
|
|
|
base::ProcessId
|
|
ParentProcessId()
|
|
{
|
|
return gParentPid;
|
|
}
|
|
|
|
void
|
|
ReportFatalError(const char* aFormat, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, aFormat);
|
|
char buf[2048];
|
|
VsprintfLiteral(buf, aFormat, ap);
|
|
va_end(ap);
|
|
|
|
// Construct a FatalErrorMessage on the stack, to avoid touching the heap.
|
|
char msgBuf[4096];
|
|
size_t header = sizeof(FatalErrorMessage);
|
|
size_t len = std::min(strlen(buf) + 1, sizeof(msgBuf) - header);
|
|
FatalErrorMessage* msg = new(msgBuf) FatalErrorMessage(header + len);
|
|
memcpy(&msgBuf[header], buf, len);
|
|
msgBuf[sizeof(msgBuf) - 1] = 0;
|
|
|
|
// Don't take the message lock when sending this, to avoid touching the heap.
|
|
gChannel->SendMessage(*msg);
|
|
|
|
DirectPrint("***** Fatal Record/Replay Error *****\n");
|
|
DirectPrint(buf);
|
|
DirectPrint("\n");
|
|
|
|
UnrecoverableSnapshotFailure();
|
|
|
|
// Block until we get a terminate message and die.
|
|
Thread::WaitForeverNoIdle();
|
|
}
|
|
|
|
void
|
|
NotifyFlushedRecording()
|
|
{
|
|
gChannel->SendMessage(RecordingFlushedMessage());
|
|
}
|
|
|
|
void
|
|
NotifyAlwaysMarkMajorCheckpoints()
|
|
{
|
|
if (IsActiveChild()) {
|
|
gChannel->SendMessage(AlwaysMarkMajorCheckpointsMessage());
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Vsyncs
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static VsyncObserver* gVsyncObserver;
|
|
|
|
void
|
|
SetVsyncObserver(VsyncObserver* aObserver)
|
|
{
|
|
MOZ_RELEASE_ASSERT(!gVsyncObserver || !aObserver);
|
|
gVsyncObserver = aObserver;
|
|
}
|
|
|
|
void
|
|
NotifyVsyncObserver()
|
|
{
|
|
if (gVsyncObserver) {
|
|
gVsyncObserver->NotifyVsync(TimeStamp::Now());
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Painting
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Graphics memory is only written on the compositor thread and read on the
|
|
// main thread and by the middleman. The gPendingPaint flag is used to
|
|
// synchronize access, so that data is not read until the paint has completed.
|
|
static Maybe<PaintMessage> gPaintMessage;
|
|
static bool gPendingPaint;
|
|
|
|
// Target buffer for the draw target created by the child process widget.
|
|
static void* gDrawTargetBuffer;
|
|
static size_t gDrawTargetBufferSize;
|
|
|
|
already_AddRefed<gfx::DrawTarget>
|
|
DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize)
|
|
{
|
|
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
|
|
|
|
gPaintMessage = Some(PaintMessage(aSize.width, aSize.height));
|
|
|
|
gfx::IntSize size(aSize.width, aSize.height);
|
|
size_t bufferSize = layers::ImageDataSerializer::ComputeRGBBufferSize(size, gSurfaceFormat);
|
|
MOZ_RELEASE_ASSERT(bufferSize <= parent::GraphicsMemorySize);
|
|
|
|
if (bufferSize != gDrawTargetBufferSize) {
|
|
free(gDrawTargetBuffer);
|
|
gDrawTargetBuffer = malloc(bufferSize);
|
|
gDrawTargetBufferSize = bufferSize;
|
|
}
|
|
|
|
size_t stride = layers::ImageDataSerializer::ComputeRGBStride(gSurfaceFormat, aSize.width);
|
|
RefPtr<gfx::DrawTarget> drawTarget =
|
|
gfx::Factory::CreateDrawTargetForData(gfx::BackendType::SKIA, (uint8_t*) gDrawTargetBuffer,
|
|
size, stride, gSurfaceFormat,
|
|
/* aUninitialized = */ true);
|
|
if (!drawTarget) {
|
|
MOZ_CRASH();
|
|
}
|
|
|
|
return drawTarget.forget();
|
|
}
|
|
|
|
void
|
|
NotifyPaintStart()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
NewCheckpoint(/* aTemporary = */ false);
|
|
|
|
gPendingPaint = true;
|
|
}
|
|
|
|
void
|
|
WaitForPaintToComplete()
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
MonitorAutoLock lock(*gMonitor);
|
|
while (gPendingPaint) {
|
|
gMonitor->Wait();
|
|
}
|
|
if (IsActiveChild() && gPaintMessage.isSome()) {
|
|
memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize);
|
|
gChannel->SendMessage(gPaintMessage.ref());
|
|
}
|
|
}
|
|
|
|
void
|
|
NotifyPaintComplete()
|
|
{
|
|
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
|
|
|
|
MonitorAutoLock lock(*gMonitor);
|
|
MOZ_RELEASE_ASSERT(gPendingPaint);
|
|
gPendingPaint = false;
|
|
gMonitor->Notify();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Checkpoint Messages
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// When recording, the time when the last HitCheckpoint message was sent.
|
|
static double gLastCheckpointTime;
|
|
|
|
// 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.
|
|
gLastCheckpointTime += CurrentTime() - gIdleTimeStart;
|
|
gIdleTimeStart = 0;
|
|
}
|
|
|
|
void
|
|
HitCheckpoint(size_t aId, bool aRecordingEndpoint)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
double time = CurrentTime();
|
|
PauseMainThreadAndInvokeCallback([=]() {
|
|
double duration = 0;
|
|
if (aId > CheckpointId::First) {
|
|
duration = time - gLastCheckpointTime;
|
|
MOZ_RELEASE_ASSERT(duration > 0);
|
|
}
|
|
gChannel->SendMessage(HitCheckpointMessage(aId, aRecordingEndpoint, duration));
|
|
});
|
|
gLastCheckpointTime = time;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Debugger Messages
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
RespondToRequest(const js::CharBuffer& aBuffer)
|
|
{
|
|
DebuggerResponseMessage* msg =
|
|
DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length());
|
|
gChannel->SendMessage(*msg);
|
|
free(msg);
|
|
}
|
|
|
|
void
|
|
HitBreakpoint(bool aRecordingEndpoint, const uint32_t* aBreakpoints, size_t aNumBreakpoints)
|
|
{
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
HitBreakpointMessage* msg =
|
|
HitBreakpointMessage::New(aRecordingEndpoint, aBreakpoints, aNumBreakpoints);
|
|
PauseMainThreadAndInvokeCallback([=]() {
|
|
gChannel->SendMessage(*msg);
|
|
free(msg);
|
|
});
|
|
}
|
|
|
|
} // namespace child
|
|
} // namespace recordreplay
|
|
} // namespace mozilla
|