-
Notifications
You must be signed in to change notification settings - Fork 54
/
MiddlemanCall.h
456 lines (387 loc) · 16.4 KB
/
MiddlemanCall.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
/* -*- 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_MiddlemanCall_h
#define mozilla_recordreplay_MiddlemanCall_h
#include "BufferStream.h"
#include "ProcessRedirect.h"
#include "mozilla/Maybe.h"
namespace mozilla {
namespace recordreplay {
// Middleman Calls Overview
//
// With few exceptions, replaying processes do not interact with the underlying
// system or call the actual versions of redirected system library functions.
// This is problematic after diverging from the recording, as then the diverged
// thread cannot interact with its recording either.
//
// Middleman calls are used in a replaying process after diverging from the
// recording to perform calls in the middleman process instead. Inputs are
// gathered and serialized in the replaying process, then sent to the middleman
// process. The middleman calls the function, and its outputs are serialized
// for reading by the replaying process.
//
// Calls that might need to be sent to the middleman are processed in phases,
// per the MiddlemanCallPhase enum below. The timeline of a middleman call is
// as follows:
//
// - Any redirection with a middleman call hook can potentially be sent to the
// middleman. In a replaying process, whenever such a call is encountered,
// the hook is invoked in the ReplayPreface phase to capture any input data
// that must be examined at the time of the call itself.
//
// - If the thread has not diverged from the recording, the call is remembered
// but no further action is necessary yet.
//
// - If the thread has diverged from the recording, the call needs to go
// through the remaining phases. The ReplayInput phase captures any
// additional inputs to the call, potentially including values produced by
// other middleman calls.
//
// - The transitive closure of these call dependencies is produced, and all
// calls found go through the ReplayInput phase. The resulting data is sent
// to the middleman process, which goes through the MiddlemanInput phase
// to decode those inputs.
//
// - The middleman performs each of the calls it has been given, and their
// outputs are encoded in the MiddlemanOutput phase. These outputs are sent
// to the replaying process in a response and decoded in the ReplayOutput
// phase, which can then resume execution.
//
// - The replaying process holds onto information about calls it has sent until
// it rewinds to a point before it diverged from the recording. This rewind
// will --- without any special action required --- wipe out information on
// all calls sent to the middleman, and retain any data gathered in the
// ReplayPreface phase for calls that were made prior to the rewind target.
//
// - Information about calls and all resources held are retained in the
// middleman process are retained until a replaying process asks for them to
// be reset, which happens any time the replaying process first diverges from
// the recording. The MiddlemanRelease phase is used to release any system
// resources held.
// Ways of processing calls that can be sent to the middleman.
enum class MiddlemanCallPhase {
// When replaying, a call is being performed that might need to be sent to
// the middleman later.
ReplayPreface,
// A call for which inputs have been gathered is now being sent to the
// middleman. This is separate from ReplayPreface because capturing inputs
// might need to dereference pointers that could be bogus values originating
// from the recording. Waiting to dereference these pointers until we know
// the call needs to be sent to the middleman avoids needing to understand
// the inputs to all call sites of general purpose redirections such as
// CFArrayCreate.
ReplayInput,
// In the middleman process, a call from the replaying process is being
// performed.
MiddlemanInput,
// In the middleman process, a call from the replaying process was just
// performed, and its outputs need to be saved.
MiddlemanOutput,
// Back in the replaying process, the outputs from a call have been received
// from the middleman.
ReplayOutput,
// In the middleman process, release any system resources held after this
// call.
MiddlemanRelease,
};
struct MiddlemanCall {
// Unique ID for this call.
size_t mId;
// ID of the redirection being invoked.
size_t mCallId;
// All register arguments and return values are preserved when sending the
// call back and forth between processes.
CallRegisterArguments mArguments;
// Written in ReplayPrefaceInput, read in ReplayInput and MiddlemanInput.
InfallibleVector<char> mPreface;
// Written in ReplayInput, read in MiddlemanInput.
InfallibleVector<char> mInput;
// Written in MiddlemanOutput, read in ReplayOutput.
InfallibleVector<char> mOutput;
// In a replaying process, whether this call has been sent to the middleman.
bool mSent;
// In a replaying process, any value associated with this call that was
// included in the recording, when the call was made before diverging from
// the recording.
Maybe<const void*> mRecordingValue;
// In a replaying or middleman process, any value associated with this call
// that was produced by the middleman itself.
Maybe<const void*> mMiddlemanValue;
MiddlemanCall() : mId(0), mCallId(0), mSent(false) {}
void EncodeInput(BufferStream& aStream) const;
void DecodeInput(BufferStream& aStream);
void EncodeOutput(BufferStream& aStream) const;
void DecodeOutput(BufferStream& aStream);
void SetRecordingValue(const void* aValue) {
MOZ_RELEASE_ASSERT(mRecordingValue.isNothing());
mRecordingValue.emplace(aValue);
}
void SetMiddlemanValue(const void* aValue) {
MOZ_RELEASE_ASSERT(mMiddlemanValue.isNothing());
mMiddlemanValue.emplace(aValue);
}
};
// Information needed to process one of the phases of a middleman call,
// in either the replaying or middleman process.
struct MiddlemanCallContext {
// Call being operated on.
MiddlemanCall* mCall;
// Complete arguments and return value information for the call.
CallArguments* mArguments;
// Current processing phase.
MiddlemanCallPhase mPhase;
// During the ReplayPreface or ReplayInput phases, whether capturing input
// data has failed. In such cases the call cannot be sent to the middleman
// and, if the thread has diverged from the recording, an unhandled
// divergence and associated rewind will occur.
bool mFailed;
// This can be set in the MiddlemanInput phase to avoid performing the call
// in the middleman process.
bool mSkipCallInMiddleman;
// During the ReplayInput phase, this can be used to fill in any middleman
// calls whose output the current one depends on.
InfallibleVector<MiddlemanCall*>* mDependentCalls;
// Streams of data that can be accessed during the various phases. Streams
// need to be read or written from at the same points in the phases which use
// them, so that callbacks operating on these streams can be composed without
// issues.
// The preface is written during ReplayPreface, and read during both
// ReplayInput and MiddlemanInput.
Maybe<BufferStream> mPrefaceStream;
// Inputs are written during ReplayInput, and read during MiddlemanInput.
Maybe<BufferStream> mInputStream;
// Outputs are written during MiddlemanOutput, and read during ReplayOutput.
Maybe<BufferStream> mOutputStream;
// During the ReplayOutput phase, this is set if the call was made sometime
// in the past and pointers referred to in the arguments may no longer be
// valid.
bool mReplayOutputIsOld;
MiddlemanCallContext(MiddlemanCall* aCall, CallArguments* aArguments,
MiddlemanCallPhase aPhase)
: mCall(aCall),
mArguments(aArguments),
mPhase(aPhase),
mFailed(false),
mSkipCallInMiddleman(false),
mDependentCalls(nullptr),
mReplayOutputIsOld(false) {
switch (mPhase) {
case MiddlemanCallPhase::ReplayPreface:
mPrefaceStream.emplace(&mCall->mPreface);
break;
case MiddlemanCallPhase::ReplayInput:
mPrefaceStream.emplace(mCall->mPreface.begin(),
mCall->mPreface.length());
mInputStream.emplace(&mCall->mInput);
break;
case MiddlemanCallPhase::MiddlemanInput:
mPrefaceStream.emplace(mCall->mPreface.begin(),
mCall->mPreface.length());
mInputStream.emplace(mCall->mInput.begin(), mCall->mInput.length());
break;
case MiddlemanCallPhase::MiddlemanOutput:
mOutputStream.emplace(&mCall->mOutput);
break;
case MiddlemanCallPhase::ReplayOutput:
mOutputStream.emplace(mCall->mOutput.begin(), mCall->mOutput.length());
break;
case MiddlemanCallPhase::MiddlemanRelease:
break;
}
}
void MarkAsFailed() {
MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayPreface ||
mPhase == MiddlemanCallPhase::ReplayInput);
mFailed = true;
}
void WriteInputBytes(const void* aBuffer, size_t aSize) {
MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayInput);
mInputStream.ref().WriteBytes(aBuffer, aSize);
}
void WriteInputScalar(size_t aValue) {
MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::ReplayInput);
mInputStream.ref().WriteScalar(aValue);
}
void ReadInputBytes(void* aBuffer, size_t aSize) {
MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::MiddlemanInput);
mInputStream.ref().ReadBytes(aBuffer, aSize);
}
size_t ReadInputScalar() {
MOZ_RELEASE_ASSERT(mPhase == MiddlemanCallPhase::MiddlemanInput);
return mInputStream.ref().ReadScalar();
}
bool AccessInput() { return mInputStream.isSome(); }
void ReadOrWriteInputBytes(void* aBuffer, size_t aSize) {
switch (mPhase) {
case MiddlemanCallPhase::ReplayInput:
WriteInputBytes(aBuffer, aSize);
break;
case MiddlemanCallPhase::MiddlemanInput:
ReadInputBytes(aBuffer, aSize);
break;
default:
MOZ_CRASH();
}
}
bool AccessPreface() { return mPrefaceStream.isSome(); }
void ReadOrWritePrefaceBytes(void* aBuffer, size_t aSize) {
switch (mPhase) {
case MiddlemanCallPhase::ReplayPreface:
mPrefaceStream.ref().WriteBytes(aBuffer, aSize);
break;
case MiddlemanCallPhase::ReplayInput:
case MiddlemanCallPhase::MiddlemanInput:
mPrefaceStream.ref().ReadBytes(aBuffer, aSize);
break;
default:
MOZ_CRASH();
}
}
void ReadOrWritePrefaceBuffer(void** aBufferPtr, size_t aSize) {
switch (mPhase) {
case MiddlemanCallPhase::ReplayPreface:
mPrefaceStream.ref().WriteBytes(*aBufferPtr, aSize);
break;
case MiddlemanCallPhase::ReplayInput:
case MiddlemanCallPhase::MiddlemanInput:
*aBufferPtr = AllocateBytes(aSize);
mPrefaceStream.ref().ReadBytes(*aBufferPtr, aSize);
break;
default:
MOZ_CRASH();
}
}
bool AccessOutput() { return mOutputStream.isSome(); }
void ReadOrWriteOutputBytes(void* aBuffer, size_t aSize) {
switch (mPhase) {
case MiddlemanCallPhase::MiddlemanOutput:
mOutputStream.ref().WriteBytes(aBuffer, aSize);
break;
case MiddlemanCallPhase::ReplayOutput:
mOutputStream.ref().ReadBytes(aBuffer, aSize);
break;
default:
MOZ_CRASH();
}
}
void ReadOrWriteOutputBuffer(void** aBuffer, size_t aSize) {
if (*aBuffer) {
if (mPhase == MiddlemanCallPhase::MiddlemanInput || mReplayOutputIsOld) {
*aBuffer = AllocateBytes(aSize);
}
if (AccessOutput()) {
ReadOrWriteOutputBytes(*aBuffer, aSize);
}
}
}
// Allocate some memory associated with the call, which will be released in
// the replaying process on a rewind and in the middleman process when the
// call state is reset.
void* AllocateBytes(size_t aSize);
};
// Notify the system about a call to a redirection with a middleman call hook.
// aDiverged is set if the current thread has diverged from the recording and
// any outputs for the call must be filled in; otherwise, they already have
// been filled in using data from the recording. Returns false if the call was
// unable to be processed.
bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments,
bool aDiverged);
// In the middleman process, perform one or more calls encoded in aInputData
// and encode their outputs to aOutputData.
void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize,
InfallibleVector<char>* aOutputData);
// In the middleman process, reset all call state.
void ResetMiddlemanCalls();
///////////////////////////////////////////////////////////////////////////////
// Middleman Call Helpers
///////////////////////////////////////////////////////////////////////////////
// Capture the contents of an input buffer at BufferArg with element count at
// CountArg.
template <size_t BufferArg, size_t CountArg, typename ElemType = char>
static inline void MM_Buffer(MiddlemanCallContext& aCx) {
if (aCx.AccessPreface()) {
auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
auto byteSize = aCx.mArguments->Arg<CountArg, size_t>() * sizeof(ElemType);
aCx.ReadOrWritePrefaceBuffer(&buffer, byteSize);
}
}
// Capture the contents of a fixed size input buffer.
template <size_t BufferArg, size_t ByteSize>
static inline void MM_BufferFixedSize(MiddlemanCallContext& aCx) {
if (aCx.AccessPreface()) {
auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
if (buffer) {
aCx.ReadOrWritePrefaceBuffer(&buffer, ByteSize);
}
}
}
// Capture a C string argument.
template <size_t StringArg>
static inline void MM_CString(MiddlemanCallContext& aCx) {
if (aCx.AccessPreface()) {
auto& buffer = aCx.mArguments->Arg<StringArg, char*>();
size_t len = (aCx.mPhase == MiddlemanCallPhase::ReplayPreface)
? strlen(buffer) + 1
: 0;
aCx.ReadOrWritePrefaceBytes(&len, sizeof(len));
aCx.ReadOrWritePrefaceBuffer((void**)&buffer, len);
}
}
// Capture the data written to an output buffer at BufferArg with element count
// at CountArg.
template <size_t BufferArg, size_t CountArg, typename ElemType>
static inline void MM_WriteBuffer(MiddlemanCallContext& aCx) {
auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
auto count = aCx.mArguments->Arg<CountArg, size_t>();
aCx.ReadOrWriteOutputBuffer(&buffer, count * sizeof(ElemType));
}
// Capture the data written to a fixed size output buffer.
template <size_t BufferArg, size_t ByteSize>
static inline void MM_WriteBufferFixedSize(MiddlemanCallContext& aCx) {
auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
aCx.ReadOrWriteOutputBuffer(&buffer, ByteSize);
}
// Capture return values that are too large for register storage.
template <size_t ByteSize>
static inline void MM_OversizeRval(MiddlemanCallContext& aCx) {
MM_WriteBufferFixedSize<0, ByteSize>(aCx);
}
// Capture a byte count of stack argument data.
template <size_t ByteSize>
static inline void MM_StackArgumentData(MiddlemanCallContext& aCx) {
if (aCx.AccessPreface()) {
auto stack = aCx.mArguments->StackAddress<0>();
aCx.ReadOrWritePrefaceBytes(stack, ByteSize);
}
}
// Avoid calling a function in the middleman process.
static inline void MM_SkipInMiddleman(MiddlemanCallContext& aCx) {
if (aCx.mPhase == MiddlemanCallPhase::MiddlemanInput) {
aCx.mSkipCallInMiddleman = true;
}
}
static inline void MM_NoOp(MiddlemanCallContext& aCx) {}
template <MiddlemanCallFn Fn0, MiddlemanCallFn Fn1,
MiddlemanCallFn Fn2 = MM_NoOp, MiddlemanCallFn Fn3 = MM_NoOp,
MiddlemanCallFn Fn4 = MM_NoOp>
static inline void MM_Compose(MiddlemanCallContext& aCx) {
Fn0(aCx);
Fn1(aCx);
Fn2(aCx);
Fn3(aCx);
Fn4(aCx);
}
// Helper for capturing inputs that are produced by other middleman calls.
// Returns false in the ReplayInput or MiddlemanInput phases if the input
// system value could not be found.
bool MM_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr);
// Helper for capturing output system values that might be consumed by other
// middleman calls.
void MM_SystemOutput(MiddlemanCallContext& aCx, const void** aOutput,
bool aUpdating = false);
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_MiddlemanCall_h