6 * Deterministic lock acquisition
8 * (c) 2012-2013 Björn Döbel <doebel@os.inf.tu-dresden.de>,
9 * economic rights: Technische Universität Dresden (Germany)
10 * This file is part of TUD:OS and distributed under the terms of the
11 * GNU General Public License 2.
12 * Please see the COPYING-GPL-2 file for details.
15 #include "observers.h"
16 #include "../instruction_length.h"
18 #define EXTERNAL_DETERMINISM 0
19 #define INTERNAL_DETERMINISM 1
22 #include <l4/plr/pthread_rep.h>
28 * Wrapper for a pthread mutex.
30 * We delegate the actual locking (management, sleep handling) work to a
33 * Additionally, we add a wrapper that allows replicated threads to acquire
34 * a recursive mutex even if the thread calling lock() the 2nd time is !=
35 * the first lock acquirer.
39 pthread_mutex_t mtx; // the mutex
41 bool recursive; // this is a recursive mtx?
42 Romain::Thread_group* owner; // owning thread group
43 unsigned counter; // recursive lock counter
47 PThreadMutex(bool rec)
48 : recursive(rec), owner(0), counter(0)
51 * Even if the mutex is recursive, we only create a
52 * simple one here, because we deal with lock recursion
55 * (Actually, we cannot use pthread recursion, because it
56 * checks that the recursive call comes from the same thread,
57 * which may not be the case due to replication).
60 pthread_mutex_init(&mtx, 0);
67 int lock(Romain::Thread_group* tg)
69 pthread_mutex_lock(&mtx);
71 * If this is recursive, only increment the counter.
73 if (recursive and counter) {
75 pthread_mutex_unlock(&mtx);
79 pthread_mutex_unlock(&mtx);
85 pthread_mutex_unlock(&mtx);
87 return sem_wait(&sem);
88 //return pthread_mutex_lock(&mtx);
97 pthread_mutex_lock(&mtx);
100 if (recursive and (counter == 0)) {
103 pthread_mutex_unlock(&mtx);
105 return sem_post(&sem);
106 //return pthread_mutex_unlock(&mtx);
113 l4_addr_t orig_address;
115 unsigned function_id;
116 l4_addr_t wrapper_address;
120 : orig_address(0), bp(0), function_id(~0UL), wrapper_address(0)
124 void configure(char const *configString, char const *alternativeString,
127 DEBUG() << configString;
128 orig_address = ConfigIntValue(configString);
129 #if INTERNAL_DETERMINISM
130 wrapper_address = ConfigIntValue(alternativeString);
133 bp = new Breakpoint(orig_address);
135 DEBUG() << std::hex << orig_address;
139 void activate(Romain::App_instance *inst, Romain::App_model* am)
141 #if INTERNAL_DETERMINISM
142 if (function_id == pt_lock_id or function_id == pt_unlock_id) {
143 /* Assumption: __pthread_(un)lock are called seldomly, so
144 * we simply use the bp functionality here */
145 bp = new Breakpoint(orig_address);
146 bp->activate(inst, am);
151 DEBUG() << lockID_to_str(function_id);
152 if ((orig_address == 0) or (orig_address == ~0))
154 bp = new Breakpoint(orig_address);
155 bp->activate(inst, am);
156 DEBUG() << "BP set.";
161 void do_patch(Romain::App_model *am)
163 #if INTERNAL_DETERMINISM
164 DEBUG() << "=============== Patching " << PURPLE << lockID_to_str(function_id)
165 << NOCOLOR << " ===============";
167 lock_info* lockinfo = reinterpret_cast<lock_info*>(am->lockinfo_local());
168 if (wrapper_address == ~0)
171 unsigned char *instructionBuffer = lockinfo->trampolines + function_id * 32;
172 memset(instructionBuffer, 0xff, 32);
173 instructionBuffer[0] = 0xCC; // INT3
174 instructionBuffer[1] = 0xC3; // RET
178 /* XXX: Dead code below ... at least for now. -> This is how we
179 * would patch the binary if we did not have access to the
180 * pthread implementation.
183 DEBUG() << "Patching function at " << std::hex << orig_address;
185 l4_addr_t func_local = am->rm()->remote_to_local(orig_address, 0);
186 DEBUG() << "function @ " << std::hex << orig_address
187 << " = " << func_local;
188 DEBUG() << "Wrapper @ " << std::hex << wrapper_address;
190 DEBUG() << "Original code:";
191 Romain::dump_mem((void*)func_local, 20);
194 * Patch the target code. We overwrite the first bytes of the
195 * function with a CALL into our handler function. Later, we make
196 * the handler return so that it executes the original code.
198 * To achieve this, we copy the function's first N instructions
199 * into a separate buffer. The buffer is then extended with a
200 * jump back to the original (non-overwritten) code. For the jump
201 * back, we use a jmp through EAX, hence we need 2 additional
202 * instructions to save and restore EAX' original value.
204 * Hence, 6 bytes of code to be replaced in the original code
205 * (5 for the CALL, 1 for PUSHing EAX).
209 l4_addr_t ip = func_local;
211 Romain::InstructionPrinter(ip, 0);
212 unsigned len = mlde32((void*)ip);
217 DEBUG() << "Need to store away " << bytes << " bytes. Then return to 0x"
218 << std::hex << orig_address + bytes;
222 * -----------------------------------------------------
223 * Create return code in the trampoline page
224 * -----------------------------------------------------
226 // POP EAX -> this reg was used by wrapper fn to determine
228 instructionBuffer[0] = 0x58;
229 // copy the instructions into buffer
230 memcpy(instructionBuffer + 1, (void*)func_local, bytes);
231 // + 1 byte: PUSH eax
232 instructionBuffer[bytes + 1] = 0x50;
233 // + 5 bytes: MOV eax, $retaddr
234 instructionBuffer[bytes + 2] = 0xB8;
235 /* Returning to the instruction _after_ our patched JMP */
236 *(l4_addr_t*)&instructionBuffer[bytes+3] = orig_address + 5;
237 // + 2 bytes: JMP [eax]
238 instructionBuffer[bytes + 7] = 0xFF; // 2 bytes: jmp *%eax
239 instructionBuffer[bytes + 8] = 0xE0;
240 DEBUG() << "Return buffer: ";
241 Romain::dump_mem(instructionBuffer, 32, 24);
245 * -----------------------------------------------------
246 * Patch the target function to jump to our wrapper
247 * -----------------------------------------------------
249 DEBUG() << "Patching original code... (ibuf @ " << std::hex
250 << (void*)instructionBuffer << ")";
251 memset((void*)func_local, 0x90, bytes);
252 // 5 bytes: JMP [wrapper_func]
253 *((char*)func_local ) = 0xE9;
255 int offset = wrapper_address - orig_address - 5;
256 DEBUG() << "Offset: " << std::hex << wrapper_address
257 << " - " << orig_address << " = " << offset;
258 *(l4_addr_t*)(func_local + 1) = offset;
261 *((char*)func_local + 5) = 0x58;
263 DEBUG() << "Patched function entry: ";
264 Romain::dump_mem((char*)func_local, 32, 24);
273 * Observer responsible for making multithreaded lock acquisition
274 * deterministic. We achieve this by placing breakpoints on the
275 * following well-known pthread functions:
277 * __pthread_lock (internal pthread use)
278 * __pthread_unlock (internal pthread use)
281 * pthread_mutex_unlock
283 * Unsupported so far:
285 * pthread_mutex_destroy
286 * pthread_mutex_trylock
287 * pthread_mutex_timedlock
289 * Then we intercept all calls to these functions and emulate them
290 * ourselves. As the observer is only called after all replicas
291 * agreed to grab a lock, we are sure that we can immediately try
292 * to grab the lock and cannot see interfering replicas from other threads.
294 * However, we still have a race when two or more thread groups
295 * successfully decide to acquire the lock. We leave resolution of these
296 * issues to the underlying pthread implementation. The whole system's
297 * behavior nevertheless becomes deterministic, because we only need to
298 * make sure that for this single run we see the same ordering of lock
299 * acquisitions (and this is ensured by using the pthread lock).
301 class PThreadLock_priv : public Romain::PThreadLockObserver
303 LockFunction _functions[pt_max_wrappers];
304 std::map<l4_umword_t, PThreadMutex*> _locks;
305 Romain::App_model::Dataspace _lip_ds;
306 l4_addr_t _lip_local;
307 pthread_mutex_t _tablemtx;
309 unsigned /* Internal determinism counters */
310 det_lock_count, // counter: det_lock
311 det_unlock_count, // counter: det_unlock
312 /* External determinism counters: */
313 mtx_lock_count, // counter: pthread_mutex_lock
314 mtx_unlock_count, // counter: pthread_mutex_unlock
315 pt_lock_count, // counter: __pthread_lock
316 pt_unlock_count, // counter: __pthread_unlock
318 ignore_count, // counter: call ignored
319 total_count; // counter: total invocations
321 PThreadMutex* lookup_or_fail(unsigned addr)
323 pthread_mutex_lock(&_tablemtx);
324 PThreadMutex* r = _locks[addr];
326 ERROR() << "Called with uninitialized mutex?";
327 enter_kdebug("op on uninitialized mutex");
329 pthread_mutex_unlock(&_tablemtx);
334 PThreadMutex* lookup_or_create(unsigned addr, bool init_locked = false,
335 Romain::Thread_group* tg = 0)
337 pthread_mutex_lock(&_tablemtx);
338 PThreadMutex* mtx = _locks[addr];
340 mtx = new PThreadMutex(false);
347 pthread_mutex_unlock(&_tablemtx);
352 void det_lock(Romain::App_instance* inst,
353 Romain::App_thread* t,
354 Romain::Thread_group* tg,
355 Romain::App_model* am)
357 l4_addr_t stackaddr = am->rm()->remote_to_local(t->vcpu()->r()->sp, inst->id());
358 l4_addr_t lock = *(l4_addr_t*)(stackaddr + 4);
359 DEBUG() << "\033[35mLOCK @ \033[0m" << std::hex << lock;
360 lookup_or_create(lock, true, tg)->lock(tg);
361 //enter_kdebug("det_lock");
365 void det_unlock(Romain::App_instance* inst,
366 Romain::App_thread* t,
367 Romain::Thread_group* tg,
368 Romain::App_model* am)
370 l4_addr_t stackaddr = am->rm()->remote_to_local(t->vcpu()->r()->sp, inst->id());
371 l4_addr_t lock = *(l4_addr_t*)(stackaddr + 4);
372 DEBUG() << "\033[35mUNLOCK @ \033[0m" << std::hex << lock;
374 pthread_mutex_lock(&_tablemtx);
375 PThreadMutex* m = _locks[lock];
377 /* This may actually happen! The unlocker is simply faster sending the
378 * notification than the locker is in sending his wakeup. Hence,
379 * we need to potentially create the respective lock here.
381 l4_umword_t mtx_kind_ptr = am->rm()->remote_to_local(lock + 12, inst->id());
382 m = new PThreadMutex(*(l4_umword_t*)mtx_kind_ptr == PTHREAD_MUTEX_RECURSIVE_NP);
385 pthread_mutex_unlock(&_tablemtx);
387 //enter_kdebug("det_unlock");
391 void attach_lock_info_page(Romain::App_model *am);
396 : det_lock_count(0), det_unlock_count(0),
397 mtx_lock_count(0), mtx_unlock_count(0),
398 pt_lock_count(0), pt_unlock_count(0),
399 ignore_count(0), total_count(0)
401 pthread_mutex_init(&_tablemtx, 0);
403 _functions[mutex_init_id].configure("threads:mutex_init",
404 "threads:mutex_init_rep",
407 _functions[mutex_lock_id].configure("threads:mutex_lock",
408 "threads:mutex_lock_rep",
410 _functions[mutex_unlock_id].configure("threads:mutex_unlock",
411 "threads:mutex_unlock_rep",
414 _functions[pt_lock_id].configure("threads:lock",
417 _functions[pt_unlock_id].configure("threads:unlock",
418 "threads:unlock_rep",
423 DECLARE_OBSERVER("pthread_lock");
425 void lock(Romain::App_instance* inst, Romain::App_thread* thread,
426 Romain::Thread_group* group, Romain::App_model* model);
427 void unlock(Romain::App_instance* inst, Romain::App_thread* thread,
428 Romain::Thread_group* group, Romain::App_model* model);
430 void mutex_init(Romain::App_instance* inst, Romain::App_thread* thread,
431 Romain::Thread_group* group, Romain::App_model* model);
432 void mutex_lock(Romain::App_instance* inst, Romain::App_thread* thread,
433 Romain::Thread_group* group, Romain::App_model* model);
434 void mutex_unlock(Romain::App_instance* inst, Romain::App_thread* thread,
435 Romain::Thread_group* group, Romain::App_model* model);