Bug 1465287 Part 1 - IPC channels, r=mccr8,jld.

--HG--
extra : rebase_source : c08c055d06a4ffc3241c7220ac57a8a820cc03fd
This commit is contained in:
Brian Hackett 2018-07-22 11:48:08 +00:00
Родитель 38ee8c769d
Коммит dbf2a5eadf
2 изменённых файлов: 721 добавлений и 0 удалений

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

@ -0,0 +1,267 @@
/* -*- 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 "Channel.h"
#include "ChildIPC.h"
#include "ProcessRewind.h"
#include "Thread.h"
#include "MainThreadUtils.h"
#include "base/process_util.h"
#include <sys/socket.h>
#include <sys/un.h>
namespace mozilla {
namespace recordreplay {
static void
GetSocketAddress(struct sockaddr_un* addr, base::ProcessId aMiddlemanPid, size_t aId)
{
addr->sun_family = AF_UNIX;
int n = snprintf(addr->sun_path, sizeof(addr->sun_path), "/tmp/WebReplay_%d_%d", aMiddlemanPid, (int) aId);
MOZ_RELEASE_ASSERT(n >= 0 && n < (int) sizeof(addr->sun_path));
addr->sun_len = SUN_LEN(addr);
}
struct HelloMessage
{
int32_t mMagic;
};
Channel::Channel(size_t aId, const MessageHandler& aHandler)
: mId(aId)
, mHandler(aHandler)
, mInitialized(false)
, mConnectionFd(0)
, mFd(0)
, mMessageBytes(0)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (IsRecordingOrReplaying()) {
MOZ_RELEASE_ASSERT(AreThreadEventsPassedThrough());
mFd = socket(AF_UNIX, SOCK_STREAM, 0);
MOZ_RELEASE_ASSERT(mFd > 0);
struct sockaddr_un addr;
GetSocketAddress(&addr, child::MiddlemanProcessId(), mId);
int rv = HANDLE_EINTR(connect(mFd, (sockaddr*) &addr, SUN_LEN(&addr)));
MOZ_RELEASE_ASSERT(rv >= 0);
DirectDeleteFile(addr.sun_path);
} else {
MOZ_RELEASE_ASSERT(IsMiddleman());
mConnectionFd = socket(AF_UNIX, SOCK_STREAM, 0);
MOZ_RELEASE_ASSERT(mConnectionFd > 0);
struct sockaddr_un addr;
GetSocketAddress(&addr, base::GetCurrentProcId(), mId);
int rv = bind(mConnectionFd, (sockaddr*) &addr, SUN_LEN(&addr));
MOZ_RELEASE_ASSERT(rv >= 0);
rv = listen(mConnectionFd, 1);
MOZ_RELEASE_ASSERT(rv >= 0);
}
Thread::SpawnNonRecordedThread(ThreadMain, this);
}
/* static */ void
Channel::ThreadMain(void* aChannelArg)
{
Channel* channel = (Channel*) aChannelArg;
static const int32_t MagicValue = 0x914522b9;
if (IsRecordingOrReplaying()) {
HelloMessage msg;
int rv = HANDLE_EINTR(recv(channel->mFd, &msg, sizeof(msg), MSG_WAITALL));
MOZ_RELEASE_ASSERT(rv == sizeof(msg));
MOZ_RELEASE_ASSERT(msg.mMagic == MagicValue);
} else {
MOZ_RELEASE_ASSERT(IsMiddleman());
channel->mFd = HANDLE_EINTR(accept(channel->mConnectionFd, nullptr, 0));
MOZ_RELEASE_ASSERT(channel->mFd > 0);
HelloMessage msg;
msg.mMagic = MagicValue;
int rv = HANDLE_EINTR(send(channel->mFd, &msg, sizeof(msg), 0));
MOZ_RELEASE_ASSERT(rv == sizeof(msg));
}
{
MonitorAutoLock lock(channel->mMonitor);
channel->mInitialized = true;
channel->mMonitor.Notify();
}
while (true) {
Message* msg = channel->WaitForMessage();
if (!msg) {
break;
}
channel->mHandler(msg);
}
}
void
Channel::SendMessage(const Message& aMsg)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread() || aMsg.mType == MessageType::FatalError);
// Block until the channel is initialized.
if (!mInitialized) {
MonitorAutoLock lock(mMonitor);
while (!mInitialized) {
mMonitor.Wait();
}
}
PrintMessage("SendMsg", aMsg);
const char* ptr = (const char*) &aMsg;
size_t nbytes = aMsg.mSize;
while (nbytes) {
int rv = HANDLE_EINTR(send(mFd, ptr, nbytes, 0));
MOZ_RELEASE_ASSERT((size_t) rv <= nbytes);
ptr += rv;
nbytes -= rv;
}
}
Message*
Channel::WaitForMessage()
{
if (!mMessageBuffer.length()) {
mMessageBuffer.appendN(0, PageSize);
}
size_t messageSize = 0;
while (true) {
if (mMessageBytes >= sizeof(Message)) {
Message* msg = (Message*) mMessageBuffer.begin();
messageSize = msg->mSize;
if (mMessageBytes >= messageSize) {
break;
}
}
// Make sure the buffer is large enough for the entire incoming message.
if (messageSize > mMessageBuffer.length()) {
mMessageBuffer.appendN(0, messageSize - mMessageBuffer.length());
}
ssize_t nbytes = HANDLE_EINTR(recv(mFd, &mMessageBuffer[mMessageBytes],
mMessageBuffer.length() - mMessageBytes, 0));
if (nbytes < 0) {
MOZ_RELEASE_ASSERT(errno == EAGAIN);
continue;
} else if (nbytes == 0) {
// The other side of the channel has shut down.
if (IsMiddleman()) {
return nullptr;
}
PrintSpew("Channel disconnected, exiting...\n");
DeleteSnapshotFiles();
_exit(0);
}
mMessageBytes += nbytes;
}
Message* res = ((Message*)mMessageBuffer.begin())->Clone();
// Remove the message we just received from the incoming buffer.
size_t remaining = mMessageBytes - messageSize;
if (remaining) {
memmove(mMessageBuffer.begin(), &mMessageBuffer[messageSize], remaining);
}
mMessageBytes = remaining;
PrintMessage("RecvMsg", *res);
return res;
}
void
Channel::PrintMessage(const char* aPrefix, const Message& aMsg)
{
if (!SpewEnabled()) {
return;
}
AutoEnsurePassThroughThreadEvents pt;
nsCString data;
switch (aMsg.mType) {
case MessageType::HitCheckpoint: {
const HitCheckpointMessage& nmsg = (const HitCheckpointMessage&) aMsg;
data.AppendPrintf("Id %d Endpoint %d Duration %.2f ms",
(int) nmsg.mCheckpointId, nmsg.mRecordingEndpoint,
nmsg.mDurationMicroseconds / 1000.0);
break;
}
case MessageType::HitBreakpoint: {
const HitBreakpointMessage& nmsg = (const HitBreakpointMessage&) aMsg;
data.AppendPrintf("Endpoint %d", nmsg.mRecordingEndpoint);
for (size_t i = 0; i < nmsg.NumBreakpoints(); i++) {
data.AppendPrintf(" Id %d", nmsg.Breakpoints()[i]);
}
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::SetBreakpoint: {
const SetBreakpointMessage& nmsg = (const SetBreakpointMessage&) aMsg;
data.AppendPrintf("Id %d, Kind %s, Script %d, Offset %d, Frame %d",
(int) nmsg.mId, 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;
data = NS_ConvertUTF16toUTF8(nsDependentString(nmsg.Buffer(), nmsg.BufferSize()));
break;
}
case MessageType::DebuggerResponse: {
const DebuggerResponseMessage& nmsg = (const DebuggerResponseMessage&) aMsg;
data = NS_ConvertUTF16toUTF8(nsDependentString(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;
}
const char* kind = IsMiddleman() ? "Middleman" : (IsRecording() ? "Recording" : "Replaying");
PrintSpew("%s%s:%d %s %s\n", kind, aPrefix, (int) mId, aMsg.TypeString(), data.get());
}
} // namespace recordreplay
} // namespace mozilla

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

@ -0,0 +1,454 @@
/* -*- 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/. */
#ifndef mozilla_recordreplay_Channel_h
#define mozilla_recordreplay_Channel_h
#include "base/process.h"
#include "js/ReplayHooks.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/Maybe.h"
#include "File.h"
#include "Monitor.h"
namespace mozilla {
namespace recordreplay {
// This file has definitions for creating and communicating on a special
// bidirectional channel between a middleman process and a recording or
// replaying process. This communication is not included in the recording, and
// when replaying this is the only mechanism the child can use to communicate
// with the middleman process.
//
// Replaying processes can rewind themselves, restoring execution state and the
// contents of all heap memory to that at an earlier point. To keep the
// replaying process and middleman from getting out of sync with each other,
// there are tight constraints on when messages may be sent across the channel
// by one process or the other. At any given time the child process may be
// either paused or unpaused. If it is paused, it is not doing any execution
// and cannot rewind itself. If it is unpaused, it may execute content and may
// rewind itself.
//
// Messages can be sent from the child process to the middleman only when the
// child process is unpaused, and messages can only be sent from the middleman
// to the child process when the child process is paused. This prevents
// 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. */ \
\
/* Sent at startup. */ \
_Macro(Introduction) \
\
/* Sent to recording processes when exiting. */ \
_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) \
\
/* Set or clear a JavaScript breakpoint. */ \
_Macro(SetBreakpoint) \
\
/* 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) \
\
/* 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) \
\
/* 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) \
\
/* A critical error occurred and execution cannot continue. The child will */ \
/* stop executing after sending this message and will wait to be terminated. */ \
_Macro(FatalError) \
\
/* The child's graphics were repainted. */ \
_Macro(Paint) \
\
/* Notify the middleman that a checkpoint or breakpoint was hit. */ \
/* The child will pause after sending these messages. */ \
_Macro(HitCheckpoint) \
_Macro(HitBreakpoint) \
\
/* Send a response to a DebuggerRequest message. */ \
_Macro(DebuggerResponse) \
\
/* Notify that the 'AlwaysMarkMajorCheckpoints' directive was invoked. */ \
_Macro(AlwaysMarkMajorCheckpoints)
enum class MessageType
{
#define DefineEnum(Kind) Kind,
ForEachMessageType(DefineEnum)
#undef DefineEnum
};
struct Message
{
MessageType mType;
// Total message size, including the header.
uint32_t mSize;
protected:
Message(MessageType aType, uint32_t aSize)
: mType(aType), mSize(aSize)
{
MOZ_RELEASE_ASSERT(mSize >= sizeof(*this));
}
public:
Message* Clone() const {
char* res = (char*) malloc(mSize);
memcpy(res, this, mSize);
return (Message*) res;
}
const char* TypeString() const {
switch (mType) {
#define EnumToString(Kind) case MessageType::Kind: return #Kind;
ForEachMessageType(EnumToString)
#undef EnumToString
default: return "Unknown";
}
}
protected:
template <typename T, typename Elem>
Elem* Data() { return (Elem*) (sizeof(T) + (char*) this); }
template <typename T, typename Elem>
const Elem* Data() const { return (const Elem*) (sizeof(T) + (const char*) this); }
template <typename T, typename Elem>
size_t DataSize() const { return (mSize - sizeof(T)) / sizeof(Elem); }
template <typename T, typename Elem, typename... Args>
static T* NewWithData(size_t aBufferSize, Args&&... aArgs) {
size_t size = sizeof(T) + aBufferSize * sizeof(Elem);
void* ptr = malloc(size);
return new(ptr) T(size, std::forward<Args>(aArgs)...);
}
};
struct IntroductionMessage : public Message
{
base::ProcessId mParentPid;
uint32_t mPrefsLen;
uint32_t mArgc;
IntroductionMessage(uint32_t aSize, base::ProcessId aParentPid, uint32_t aPrefsLen, uint32_t aArgc)
: Message(MessageType::Introduction, aSize)
, mParentPid(aParentPid)
, mPrefsLen(aPrefsLen)
, mArgc(aArgc)
{}
char* PrefsData() { return Data<IntroductionMessage, char>(); }
char* ArgvString() { return Data<IntroductionMessage, char>() + mPrefsLen; }
const char* PrefsData() const { return Data<IntroductionMessage, char>(); }
const char* ArgvString() const { return Data<IntroductionMessage, char>() + mPrefsLen; }
static IntroductionMessage* New(base::ProcessId aParentPid, char* aPrefs, size_t aPrefsLen,
int aArgc, char* aArgv[]) {
size_t argsLen = 0;
for (int i = 0; i < aArgc; i++) {
argsLen += strlen(aArgv[i]) + 1;
}
IntroductionMessage* res =
NewWithData<IntroductionMessage, char>(aPrefsLen + argsLen, aParentPid, aPrefsLen, aArgc);
memcpy(res->PrefsData(), aPrefs, aPrefsLen);
size_t offset = 0;
for (int i = 0; i < aArgc; i++) {
memcpy(&res->ArgvString()[offset], aArgv[i], strlen(aArgv[i]) + 1);
offset += strlen(aArgv[i]) + 1;
}
MOZ_RELEASE_ASSERT(offset == argsLen);
return res;
}
};
template <MessageType Type>
struct EmptyMessage : public Message
{
EmptyMessage()
: Message(Type, sizeof(*this))
{}
};
typedef EmptyMessage<MessageType::Terminate> TerminateMessage;
typedef EmptyMessage<MessageType::CreateCheckpoint> CreateCheckpointMessage;
typedef EmptyMessage<MessageType::FlushRecording> FlushRecordingMessage;
template <MessageType Type>
struct JSONMessage : public Message
{
explicit JSONMessage(uint32_t aSize)
: Message(Type, aSize)
{}
const char16_t* Buffer() const { return Data<JSONMessage<Type>, char16_t>(); }
size_t BufferSize() const { return DataSize<JSONMessage<Type>, char16_t>(); }
static JSONMessage<Type>* New(const char16_t* aBuffer, size_t aBufferSize) {
JSONMessage<Type>* res = NewWithData<JSONMessage<Type>, char16_t>(aBufferSize);
MOZ_RELEASE_ASSERT(res->BufferSize() == aBufferSize);
PodCopy(res->Data<JSONMessage<Type>, char16_t>(), aBuffer, aBufferSize);
return res;
}
};
typedef JSONMessage<MessageType::DebuggerRequest> DebuggerRequestMessage;
typedef JSONMessage<MessageType::DebuggerResponse> DebuggerResponseMessage;
struct SetBreakpointMessage : public Message
{
// ID of the breakpoint to change.
size_t mId;
// New position of the breakpoint. If this is invalid then the breakpoint is
// being cleared.
JS::replay::ExecutionPosition mPosition;
SetBreakpointMessage(size_t aId, const JS::replay::ExecutionPosition& aPosition)
: Message(MessageType::SetBreakpoint, sizeof(*this))
, mId(aId)
, mPosition(aPosition)
{}
};
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 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;
struct FatalErrorMessage : public Message
{
explicit FatalErrorMessage(uint32_t aSize)
: Message(MessageType::FatalError, aSize)
{}
const char* Error() const { return Data<FatalErrorMessage, const char>(); }
};
static const gfx::SurfaceFormat gSurfaceFormat = gfx::SurfaceFormat::B8G8R8X8;
struct PaintMessage : public Message
{
uint32_t mWidth;
uint32_t mHeight;
PaintMessage(uint32_t aWidth, uint32_t aHeight)
: Message(MessageType::Paint, sizeof(*this))
, mWidth(aWidth)
, mHeight(aHeight)
{}
};
struct HitCheckpointMessage : public Message
{
uint32_t mCheckpointId;
bool mRecordingEndpoint;
// When recording, the amount of non-idle time taken to get to this
// checkpoint from the previous one.
double mDurationMicroseconds;
HitCheckpointMessage(uint32_t aCheckpointId, bool aRecordingEndpoint, double aDurationMicroseconds)
: Message(MessageType::HitCheckpoint, sizeof(*this))
, mCheckpointId(aCheckpointId)
, mRecordingEndpoint(aRecordingEndpoint)
, mDurationMicroseconds(aDurationMicroseconds)
{}
};
struct HitBreakpointMessage : public Message
{
bool mRecordingEndpoint;
HitBreakpointMessage(uint32_t aSize, bool aRecordingEndpoint)
: Message(MessageType::HitBreakpoint, aSize)
, mRecordingEndpoint(aRecordingEndpoint)
{}
const uint32_t* Breakpoints() const { return Data<HitBreakpointMessage, uint32_t>(); }
uint32_t NumBreakpoints() const { return DataSize<HitBreakpointMessage, uint32_t>(); }
static HitBreakpointMessage* New(bool aRecordingEndpoint,
const uint32_t* aBreakpoints, size_t aNumBreakpoints) {
HitBreakpointMessage* res =
NewWithData<HitBreakpointMessage, uint32_t>(aNumBreakpoints, aRecordingEndpoint);
MOZ_RELEASE_ASSERT(res->NumBreakpoints() == aNumBreakpoints);
PodCopy(res->Data<HitBreakpointMessage, uint32_t>(), aBreakpoints, aNumBreakpoints);
return res;
}
};
typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints> AlwaysMarkMajorCheckpointsMessage;
class Channel
{
public:
// Note: the handler is responsible for freeing its input message. It will be
// called on the channel's message thread.
typedef std::function<void(Message*)> MessageHandler;
private:
// ID for this channel, unique for the middleman.
size_t mId;
// Callback to invoke off thread on incoming messages.
MessageHandler mHandler;
// Whether the channel is initialized and ready for outgoing messages.
Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mInitialized;
// Descriptor used to accept connections on the parent side.
int mConnectionFd;
// Descriptor used to communicate with the other side.
int mFd;
// For synchronizing initialization of the channel.
Monitor mMonitor;
// Buffer for message data received from the other side of the channel.
InfallibleVector<char, 0, AllocPolicy<UntrackedMemoryKind::Generic>> mMessageBuffer;
// The number of bytes of data already in the message buffer.
size_t mMessageBytes;
// If spew is enabled, print a message and associated info to stderr.
void PrintMessage(const char* aPrefix, const Message& aMsg);
// Block until a complete message is received from the other side of the
// channel.
Message* WaitForMessage();
// Main routine for the channel's thread.
static void ThreadMain(void* aChannel);
public:
// Initialize this channel, connect to the other side, and spin up a thread
// to process incoming messages by calling aHandler.
Channel(size_t aId, const MessageHandler& aHandler);
size_t GetId() { return mId; }
// Send a message to the other side of the channel. This must be called on
// the main thread, except for fatal error messages.
void SendMessage(const Message& aMsg);
};
// Command line option used to specify the channel ID for a child process.
static const char* gChannelIDOption = "-recordReplayChannelID";
// ID for the mach message sent when notifying a subprocess about the graphics
// shared memory buffer.
static const int32_t GraphicsMessageId = 42;
// Fixed size of the graphics shared memory buffer.
static const size_t GraphicsMemorySize = 4096 * 4096 * 4;
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_Channel_h