Remove mprotect snapshots
[c11tester.git] / snapshot.cc
1 #include <inttypes.h>
2 #include <sys/mman.h>
3 #include <unistd.h>
4 #include <signal.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <errno.h>
8 #include <sys/wait.h>
9
10 #include "hashtable.h"
11 #include "snapshot.h"
12 #include "mymemory.h"
13 #include "common.h"
14 #include "context.h"
15 #include "model.h"
16
17
18 #define SHARED_MEMORY_DEFAULT  (200 * ((size_t)1 << 20))        // 100mb for the shared memory
19 #define STACK_SIZE_DEFAULT      (((size_t)1 << 20) * 20)        // 20 mb out of the above 100 mb for my stack
20
21 struct fork_snapshotter {
22         /** @brief Pointer to the shared (non-snapshot) memory heap base
23          * (NOTE: this has size SHARED_MEMORY_DEFAULT - sizeof(*fork_snap)) */
24         void *mSharedMemoryBase;
25
26         /** @brief Pointer to the shared (non-snapshot) stack region */
27         void *mStackBase;
28
29         /** @brief Size of the shared stack */
30         size_t mStackSize;
31
32         /**
33          * @brief Stores the ID that we are attempting to roll back to
34          *
35          * Used in inter-process communication so that each process can
36          * determine whether or not to take over execution (w/ matching ID) or
37          * exit (we're rolling back even further). Dubiously marked 'volatile'
38          * to prevent compiler optimizations from messing with the
39          * inter-process behavior.
40          */
41         volatile snapshot_id mIDToRollback;
42
43
44
45         /** @brief Inter-process tracking of the next snapshot ID */
46         snapshot_id currSnapShotID;
47 };
48
49 static struct fork_snapshotter *fork_snap = NULL;
50 ucontext_t shared_ctxt;
51
52 /** @statics
53  *   These variables are necessary because the stack is shared region and
54  *   there exists a race between all processes executing the same function.
55  *   To avoid the problem above, we require variables allocated in 'safe' regions.
56  *   The bug was actually observed with the forkID, these variables below are
57  *   used to indicate the various contexts to which to switch to.
58  *
59  *   @private_ctxt: the context which is internal to the current process. Used
60  *   for running the internal snapshot/rollback loop.
61  *   @exit_ctxt: a special context used just for exiting from a process (so we
62  *   can use swapcontext() instead of setcontext() + hacks)
63  *   @snapshotid: it is a running counter for the various forked processes
64  *   snapshotid. it is incremented and set in a persistently shared record
65  */
66 static ucontext_t private_ctxt;
67 static ucontext_t exit_ctxt;
68 static snapshot_id snapshotid = 0;
69
70 /**
71  * @brief Create a new context, with a given stack and entry function
72  * @param ctxt The context structure to fill
73  * @param stack The stack to run the new context in
74  * @param stacksize The size of the stack
75  * @param func The entry point function for the context
76  */
77 static void create_context(ucontext_t *ctxt, void *stack, size_t stacksize,
78                                                                                                          void (*func)(void))
79 {
80         getcontext(ctxt);
81         ctxt->uc_stack.ss_sp = stack;
82         ctxt->uc_stack.ss_size = stacksize;
83         ctxt->uc_link = NULL;
84         makecontext(ctxt, func, 0);
85 }
86
87 /** @brief An empty function, used for an "empty" context which just exits a
88  *  process */
89 static void fork_exit()
90 {
91         _Exit(EXIT_SUCCESS);
92 }
93
94 static void createSharedMemory()
95 {
96         //step 1. create shared memory.
97         void *memMapBase = mmap(0, SHARED_MEMORY_DEFAULT + STACK_SIZE_DEFAULT, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
98         if (memMapBase == MAP_FAILED) {
99                 perror("mmap");
100                 exit(EXIT_FAILURE);
101         }
102
103         //Setup snapshot record at top of free region
104         fork_snap = (struct fork_snapshotter *)memMapBase;
105         fork_snap->mSharedMemoryBase = (void *)((uintptr_t)memMapBase + sizeof(*fork_snap));
106         fork_snap->mStackBase = (void *)((uintptr_t)memMapBase + SHARED_MEMORY_DEFAULT);
107         fork_snap->mStackSize = STACK_SIZE_DEFAULT;
108         fork_snap->mIDToRollback = -1;
109         fork_snap->currSnapShotID = 0;
110 }
111
112 /**
113  * Create a new mspace pointer for the non-snapshotting (i.e., inter-process
114  * shared) memory region. Only for fork-based snapshotting.
115  *
116  * @return The shared memory mspace
117  */
118 mspace create_shared_mspace()
119 {
120         if (!fork_snap)
121                 createSharedMemory();
122         return create_mspace_with_base((void *)(fork_snap->mSharedMemoryBase), SHARED_MEMORY_DEFAULT - sizeof(*fork_snap), 1);
123 }
124
125 static void fork_snapshot_init(unsigned int numbackingpages,
126                                                                                                                          unsigned int numsnapshots, unsigned int nummemoryregions,
127                                                                                                                          unsigned int numheappages)
128 {
129         if (!fork_snap)
130                 createSharedMemory();
131
132         model_snapshot_space = create_mspace(numheappages * PAGESIZE, 1);
133 }
134
135 volatile int modellock = 0;
136
137 static void fork_loop() {
138         /* switch back here when takesnapshot is called */
139         snapshotid = fork_snap->currSnapShotID;
140         if (model->params.nofork) {
141                 setcontext(&shared_ctxt);
142                 _Exit(EXIT_SUCCESS);
143         }
144
145         while (true) {
146                 pid_t forkedID;
147                 fork_snap->currSnapShotID = snapshotid + 1;
148
149                 modellock = 1;
150                 forkedID = fork();
151                 modellock = 0;
152
153                 if (0 == forkedID) {
154                         setcontext(&shared_ctxt);
155                 } else {
156                         DEBUG("parent PID: %d, child PID: %d, snapshot ID: %d\n",
157                                                 getpid(), forkedID, snapshotid);
158
159                         while (waitpid(forkedID, NULL, 0) < 0) {
160                                 /* waitpid() may be interrupted */
161                                 if (errno != EINTR) {
162                                         perror("waitpid");
163                                         exit(EXIT_FAILURE);
164                                 }
165                         }
166
167                         if (fork_snap->mIDToRollback != snapshotid)
168                                 _Exit(EXIT_SUCCESS);
169                 }
170         }
171 }
172
173 static void fork_startExecution(ucontext_t *context, VoidFuncPtr entryPoint) {
174         /* setup an "exiting" context */
175         int exit_stack_size = 256;
176         create_context(&exit_ctxt, snapshot_calloc(exit_stack_size, 1), exit_stack_size, fork_exit);
177
178         /* setup the system context */
179         create_context(context, fork_snap->mStackBase, STACK_SIZE_DEFAULT, entryPoint);
180         /* switch to a new entryPoint context, on a new stack */
181         create_context(&private_ctxt, snapshot_calloc(STACK_SIZE_DEFAULT, 1), STACK_SIZE_DEFAULT, fork_loop);
182 }
183
184 static snapshot_id fork_take_snapshot() {
185         model_swapcontext(&shared_ctxt, &private_ctxt);
186         DEBUG("TAKESNAPSHOT RETURN\n");
187         return snapshotid;
188 }
189
190 static void fork_roll_back(snapshot_id theID)
191 {
192         DEBUG("Rollback\n");
193         fork_snap->mIDToRollback = theID;
194         model_swapcontext(model->get_system_context(), &exit_ctxt);
195         fork_snap->mIDToRollback = -1;
196 }
197
198 /**
199  * @brief Initializes the snapshot system
200  * @param entryPoint the function that should run the program.
201  */
202 void snapshot_system_init(unsigned int numbackingpages,
203                                                                                                         unsigned int numsnapshots, unsigned int nummemoryregions,
204                                                                                                         unsigned int numheappages)
205 {
206         fork_snapshot_init(numbackingpages, numsnapshots, nummemoryregions, numheappages);
207 }
208
209 void startExecution(ucontext_t *context, VoidFuncPtr entryPoint)
210 {
211         fork_startExecution(context, entryPoint);
212 }
213
214 /** Assumes that addr is page aligned. */
215 void snapshot_add_memory_region(void *addr, unsigned int numPages)
216 {
217         /* not needed for fork-based snapshotting */
218 }
219
220 /** Takes a snapshot of memory.
221  * @return The snapshot identifier.
222  */
223 snapshot_id take_snapshot()
224 {
225         return fork_take_snapshot();
226 }
227
228 /** Rolls the memory state back to the given snapshot identifier.
229  *  @param theID is the snapshot identifier to rollback to.
230  */
231 void snapshot_roll_back(snapshot_id theID)
232 {
233         fork_roll_back(theID);
234 }