-
Notifications
You must be signed in to change notification settings - Fork 54
/
ExternalCall.h
452 lines (382 loc) · 16.4 KB
/
ExternalCall.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
/* -*- 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_ExternalCall_h
#define mozilla_recordreplay_ExternalCall_h
#include "BufferStream.h"
#include "ProcessRedirect.h"
#include "mozilla/Maybe.h"
namespace mozilla {
namespace recordreplay {
// External 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.
//
// External calls are used in a replaying process after diverging from the
// recording to perform calls in another process instead. We call this the
// external process; currently it is always the middleman, though this will
// change soon.
//
// External call state is managed so that call results can be reused when
// possible, to minimize traffic between processes and improve efficiency.
// Conceptually, the result of performing a system call is entirely determined
// by its inputs: scalar values and the results of other system calls (and the
// version of the underlying system, which we ignore for now). This information
// uniquely identifies the call, and we can form a directed graph out of these
// calls by connecting them to the other calls they depend on.
//
// Each root replaying process maintains a portion of this graph. As the
// recording is replayed by other forked processes, nodes are added to the graph
// by studying the output of calls that appear in the recording itself. When
// a replaying process wants to make a system call that does not appear in the
// graph, the inputs for that call and the transitive closure of the calls it
// depends on is sent to another process running on the target platform. That
// process executes the call, saves the output and sends it back for adding to
// the graph. Other ways of updating the graph could be added in the future.
// Inputs and outputs to external calls are handled in a reusable fashion by
// adding a ExternalCall hook to the system call's redirection. This hook is
// called in one of the following phases.
enum class ExternalCallPhase {
// When replaying, a call which can be put in the external call graph is being
// made. This can happen either before or after diverging from the recording,
// and saves all inputs to the call which are sufficient to uniquely identify
// it in the graph.
SaveInput,
// In the external process, the inputs saved earlier are being restored in
// preparation for executing the call.
RestoreInput,
// Save any outputs produced by a call. This can happen either in the
// replaying process (using outputs saved in the recording) or in the external
// process (using outputs just produced). After saving the outputs to the
// call, it can be placed in the external call graph and be used to resolve
// external calls which a diverged replaying process is making. Returned
// register values are automatically saved.
SaveOutput,
// In the replaying process, restore any outputs associated with an external
// call. This is only called after diverging from the recording, and allows
// diverged execution to continue afterwards.
RestoreOutput,
};
// Global identifier for an external call. The result of an external call is
// determined by its inputs, and its ID is a hash of those inputs. If there are
// hash collisions between different inputs then two different calls will have
// the same ID, and things will break (subtly or not). Valid IDs are non-zero.
typedef uintptr_t ExternalCallId;
// Storage for the returned registers of a call which are automatically saved.
struct CallReturnRegisters {
size_t rval0, rval1;
double floatrval0, floatrval1;
void CopyFrom(CallArguments* aArguments);
void CopyTo(CallArguments* aArguments);
};
struct ExternalCall {
// ID for this call.
ExternalCallId mId = 0;
// ID of the redirection being invoked.
size_t mCallId = 0;
// All call inputs. Written in SaveInput, read in RestoreInput.
InfallibleVector<char> mInput;
// If non-zero, only the input before this extent is used to characterize this
// call and determine if it is the same as another external call.
size_t mExcludeInput = 0;
// Calls which this depends on, written in SaveInput and read in RestoreInput.
// Any calls referenced in mInput will also be here.
InfallibleVector<ExternalCallId> mDependentCalls;
// All call outputs. Written in SaveOutput, read in RestoreOutput.
InfallibleVector<char> mOutput;
// Values of any returned registers after the call.
CallReturnRegisters mReturnRegisters;
// Any system value produced by this call. In the external process this is
// the actual system value, while in the replaying process this is the value
// used during execution to represent the call's result.
Maybe<const void*> mValue;
void EncodeInput(BufferStream& aStream) const;
void DecodeInput(BufferStream& aStream);
void EncodeOutput(BufferStream& aStream) const;
void DecodeOutput(BufferStream& aStream);
void ComputeId() {
MOZ_RELEASE_ASSERT(!mId);
size_t extent = mExcludeInput ? mExcludeInput : mInput.length();
mId = HashGeneric(mCallId, HashBytes(mInput.begin(), extent));
if (!mId) {
mId = 1;
}
}
};
// Information needed to process one of the phases of an external call.
struct ExternalCallContext {
// Call being operated on.
ExternalCall* mCall;
// Complete arguments and return value information for the call.
CallArguments* mArguments;
// Current processing phase.
ExternalCallPhase mPhase;
// During the SaveInput phase, whether capturing input data has failed.
// In such cases the call cannot be placed in the external call graph and,
// if the thread has diverged from the recording, an unhandled divergence
// will occur.
bool mFailed = false;
// This can be set in the RestoreInput phase to avoid executing the call
// in the external process.
bool mSkipExecuting = false;
// 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.
// Inputs are written during SaveInput, and read during RestoreInput.
Maybe<BufferStream> mInputStream;
// Outputs are written during SaveOutput, and read during RestoreOutput.
Maybe<BufferStream> mOutputStream;
// If we are running the SaveOutput phase in an external process, a list of
// callbacks which will release all system resources created by by the call.
typedef InfallibleVector<std::function<void()>> ReleaseCallbackVector;
ReleaseCallbackVector* mReleaseCallbacks = nullptr;
ExternalCallContext(ExternalCall* aCall, CallArguments* aArguments,
ExternalCallPhase aPhase)
: mCall(aCall), mArguments(aArguments), mPhase(aPhase) {
switch (mPhase) {
case ExternalCallPhase::SaveInput:
mInputStream.emplace(&mCall->mInput);
break;
case ExternalCallPhase::RestoreInput:
mInputStream.emplace(mCall->mInput.begin(), mCall->mInput.length());
break;
case ExternalCallPhase::SaveOutput:
mCall->mReturnRegisters.CopyFrom(aArguments);
mOutputStream.emplace(&mCall->mOutput);
break;
case ExternalCallPhase::RestoreOutput:
mCall->mReturnRegisters.CopyTo(aArguments);
mOutputStream.emplace(mCall->mOutput.begin(), mCall->mOutput.length());
break;
}
}
void MarkAsFailed() {
MOZ_RELEASE_ASSERT(mPhase == ExternalCallPhase::SaveInput);
mFailed = true;
}
void WriteInputBytes(const void* aBuffer, size_t aSize) {
MOZ_RELEASE_ASSERT(mPhase == ExternalCallPhase::SaveInput);
mInputStream.ref().WriteBytes(aBuffer, aSize);
}
void WriteInputScalar(size_t aValue) {
MOZ_RELEASE_ASSERT(mPhase == ExternalCallPhase::SaveInput);
mInputStream.ref().WriteScalar(aValue);
}
void ReadInputBytes(void* aBuffer, size_t aSize) {
MOZ_RELEASE_ASSERT(mPhase == ExternalCallPhase::RestoreInput);
mInputStream.ref().ReadBytes(aBuffer, aSize);
}
size_t ReadInputScalar() {
MOZ_RELEASE_ASSERT(mPhase == ExternalCallPhase::RestoreInput);
return mInputStream.ref().ReadScalar();
}
bool AccessInput() { return mInputStream.isSome(); }
void ReadOrWriteInputBytes(void* aBuffer, size_t aSize,
bool aExcludeInput = false) {
switch (mPhase) {
case ExternalCallPhase::SaveInput:
// Only one buffer can be excluded, and it has to be the last input to
// the external call.
MOZ_RELEASE_ASSERT(!mCall->mExcludeInput);
if (aExcludeInput) {
mCall->mExcludeInput = mCall->mInput.length();
}
WriteInputBytes(aBuffer, aSize);
break;
case ExternalCallPhase::RestoreInput:
ReadInputBytes(aBuffer, aSize);
break;
default:
MOZ_CRASH();
}
}
void ReadOrWriteInputBuffer(void** aBufferPtr, size_t aSize,
bool aIncludeContents = true) {
switch (mPhase) {
case ExternalCallPhase::SaveInput:
if (aIncludeContents) {
WriteInputBytes(*aBufferPtr, aSize);
}
break;
case ExternalCallPhase::RestoreInput:
*aBufferPtr = AllocateBytes(aSize);
if (aIncludeContents) {
ReadInputBytes(*aBufferPtr, aSize);
}
break;
default:
MOZ_CRASH();
}
}
bool AccessOutput() { return mOutputStream.isSome(); }
void ReadOrWriteOutputBytes(void* aBuffer, size_t aSize) {
switch (mPhase) {
case ExternalCallPhase::SaveOutput:
mOutputStream.ref().WriteBytes(aBuffer, aSize);
break;
case ExternalCallPhase::RestoreOutput:
mOutputStream.ref().ReadBytes(aBuffer, aSize);
break;
default:
MOZ_CRASH();
}
}
void ReadOrWriteOutputBuffer(void** aBuffer, size_t aSize) {
if (AccessInput()) {
bool isNull = *aBuffer == nullptr;
ReadOrWriteInputBytes(&isNull, sizeof(isNull));
if (isNull) {
*aBuffer = nullptr;
} else if (mPhase == ExternalCallPhase::RestoreInput) {
*aBuffer = AllocateBytes(aSize);
}
}
if (AccessOutput() && *aBuffer) {
ReadOrWriteOutputBytes(*aBuffer, aSize);
}
}
// Allocate some memory associated with the call, which will be released in
// the external process after fully processing a call, and will never be
// released in the replaying process.
void* AllocateBytes(size_t aSize);
};
// Notify the system about a call to a redirection with an external 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 OnExternalCall(size_t aCallId, CallArguments* aArguments, bool aDiverged);
// In the external process, perform one or more calls encoded in aInputData
// and encode the output of the final call in aOutputData.
void ProcessExternalCall(const char* aInputData, size_t aInputSize,
InfallibleVector<char>* aOutputData);
// In a replaying process, flush all new external call found in the recording
// since the last flush to the root replaying process.
void FlushExternalCalls();
// In a root replaying process, remember the output from an external call.
void AddExternalCallOutput(ExternalCallId aId, const char* aOutput,
size_t aOutputSize);
// In a root replaying process, fetch the output from an external call if known.
bool HasExternalCallOutput(ExternalCallId aId, InfallibleVector<char>* aOutput);
///////////////////////////////////////////////////////////////////////////////
// External Call Helpers
///////////////////////////////////////////////////////////////////////////////
// Capture a scalar argument.
template <size_t Arg>
static inline void EX_ScalarArg(ExternalCallContext& aCx) {
if (aCx.AccessInput()) {
auto& arg = aCx.mArguments->Arg<Arg, size_t>();
aCx.ReadOrWriteInputBytes(&arg, sizeof(arg));
}
}
// Capture a floating point argument.
template <size_t Arg>
static inline void EX_FloatArg(ExternalCallContext& aCx) {
if (aCx.AccessInput()) {
auto& arg = aCx.mArguments->FloatArg<Arg>();
aCx.ReadOrWriteInputBytes(&arg, sizeof(arg));
}
}
// Capture an input buffer at BufferArg with element count at CountArg.
// If IncludeContents is not set, the buffer's contents are not captured,
// but the buffer's pointer will be allocated with the correct size when
// restoring input.
template <size_t BufferArg, size_t CountArg, typename ElemType = char,
bool IncludeContents = true>
static inline void EX_Buffer(ExternalCallContext& aCx) {
EX_ScalarArg<CountArg>(aCx);
if (aCx.AccessInput()) {
auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
auto byteSize = aCx.mArguments->Arg<CountArg, size_t>() * sizeof(ElemType);
aCx.ReadOrWriteInputBuffer(&buffer, byteSize, IncludeContents);
}
}
// Capture the contents of an optional input parameter.
template <size_t BufferArg, typename Type>
static inline void EX_InParam(ExternalCallContext& aCx) {
if (aCx.AccessInput()) {
auto& param = aCx.mArguments->Arg<BufferArg, void*>();
bool hasParam = !!param;
aCx.ReadOrWriteInputBytes(&hasParam, sizeof(hasParam));
if (hasParam) {
aCx.ReadOrWriteInputBuffer(¶m, sizeof(Type));
} else {
param = nullptr;
}
}
}
// Capture a C string argument.
template <size_t StringArg>
static inline void EX_CString(ExternalCallContext& aCx) {
if (aCx.AccessInput()) {
auto& buffer = aCx.mArguments->Arg<StringArg, char*>();
size_t len =
(aCx.mPhase == ExternalCallPhase::SaveInput) ? strlen(buffer) + 1 : 0;
aCx.ReadOrWriteInputBytes(&len, sizeof(len));
aCx.ReadOrWriteInputBuffer((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 EX_WriteBuffer(ExternalCallContext& aCx) {
EX_ScalarArg<CountArg>(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 an out parameter.
template <size_t BufferArg, typename Type>
static inline void EX_OutParam(ExternalCallContext& aCx) {
auto& buffer = aCx.mArguments->Arg<BufferArg, void*>();
aCx.ReadOrWriteOutputBuffer(&buffer, sizeof(Type));
}
// Capture return values that are too large for register storage.
template <typename Type>
static inline void EX_OversizeRval(ExternalCallContext& aCx) {
EX_OutParam<0, Type>(aCx);
}
// Capture a byte count of stack argument data.
template <size_t ByteSize>
static inline void EX_StackArgumentData(ExternalCallContext& aCx) {
if (aCx.AccessInput()) {
auto stack = aCx.mArguments->StackAddress<0>();
aCx.ReadOrWriteInputBytes(stack, ByteSize);
}
}
// Avoid calling a function in the external process.
static inline void EX_SkipExecuting(ExternalCallContext& aCx) {
if (aCx.mPhase == ExternalCallPhase::RestoreInput) {
aCx.mSkipExecuting = true;
}
}
static inline void EX_NoOp(ExternalCallContext& aCx) {}
template <ExternalCallFn Fn0, ExternalCallFn Fn1, ExternalCallFn Fn2 = EX_NoOp,
ExternalCallFn Fn3 = EX_NoOp, ExternalCallFn Fn4 = EX_NoOp,
ExternalCallFn Fn5 = EX_NoOp>
static inline void EX_Compose(ExternalCallContext& aCx) {
Fn0(aCx);
Fn1(aCx);
Fn2(aCx);
Fn3(aCx);
Fn4(aCx);
Fn5(aCx);
}
// Helper for capturing inputs that are produced by other external calls.
// Returns false in the SaveInput phase if the input system value could not
// be found.
bool EX_SystemInput(ExternalCallContext& aCx, const void** aThingPtr);
// Helper for capturing output system values that might be consumed by other
// external calls.
void EX_SystemOutput(ExternalCallContext& aCx, const void** aOutput,
bool aUpdating = false);
void InitializeExternalCalls();
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_ExternalCall_h