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
143 (function_id == pt_unlock_id)) and
144 (orig_address != ~0UL)) {
145 /* Assumption: __pthread_(un)lock are called seldomly, so
146 * we simply use the bp functionality here */
147 bp = new Breakpoint(orig_address);
148 bp->activate(inst, am);
153 DEBUG() << lockID_to_str(function_id);
154 if ((orig_address == 0) or (orig_address == ~0))
156 bp = new Breakpoint(orig_address);
157 bp->activate(inst, am);
158 DEBUG() << "BP set.";
163 void do_patch(Romain::App_model *am)
165 #if INTERNAL_DETERMINISM
166 DEBUG() << "=============== Patching " << PURPLE << lockID_to_str(function_id)
167 << NOCOLOR << " ===============";
169 lock_info* lockinfo = reinterpret_cast<lock_info*>(am->lockinfo_local());
170 if (wrapper_address == ~0) {
171 DEBUG() << BLUE << " no known address" << NOCOLOR;
175 unsigned char *instructionBuffer = lockinfo->trampolines + function_id * 32;
176 memset(instructionBuffer, 0xff, 32);
177 instructionBuffer[0] = 0xCC; // INT3
178 instructionBuffer[1] = 0xC3; // RET
182 /* XXX: Dead code below ... at least for now. -> This is how we
183 * would patch the binary if we did not have access to the
184 * pthread implementation.
187 DEBUG() << "Patching function at " << std::hex << orig_address;
189 l4_addr_t func_local = am->rm()->remote_to_local(orig_address, 0);
190 DEBUG() << "function @ " << std::hex << orig_address
191 << " = " << func_local;
192 DEBUG() << "Wrapper @ " << std::hex << wrapper_address;
194 DEBUG() << "Original code:";
195 Romain::dump_mem((void*)func_local, 20);
198 * Patch the target code. We overwrite the first bytes of the
199 * function with a CALL into our handler function. Later, we make
200 * the handler return so that it executes the original code.
202 * To achieve this, we copy the function's first N instructions
203 * into a separate buffer. The buffer is then extended with a
204 * jump back to the original (non-overwritten) code. For the jump
205 * back, we use a jmp through EAX, hence we need 2 additional
206 * instructions to save and restore EAX' original value.
208 * Hence, 6 bytes of code to be replaced in the original code
209 * (5 for the CALL, 1 for PUSHing EAX).
213 l4_addr_t ip = func_local;
215 Romain::InstructionPrinter(ip, 0);
216 unsigned len = mlde32((void*)ip);
221 DEBUG() << "Need to store away " << bytes << " bytes. Then return to 0x"
222 << std::hex << orig_address + bytes;
226 * -----------------------------------------------------
227 * Create return code in the trampoline page
228 * -----------------------------------------------------
230 // POP EAX -> this reg was used by wrapper fn to determine
232 instructionBuffer[0] = 0x58;
233 // copy the instructions into buffer
234 memcpy(instructionBuffer + 1, (void*)func_local, bytes);
235 // + 1 byte: PUSH eax
236 instructionBuffer[bytes + 1] = 0x50;
237 // + 5 bytes: MOV eax, $retaddr
238 instructionBuffer[bytes + 2] = 0xB8;
239 /* Returning to the instruction _after_ our patched JMP */
240 *(l4_addr_t*)&instructionBuffer[bytes+3] = orig_address + 5;
241 // + 2 bytes: JMP [eax]
242 instructionBuffer[bytes + 7] = 0xFF; // 2 bytes: jmp *%eax
243 instructionBuffer[bytes + 8] = 0xE0;
244 DEBUG() << "Return buffer: ";
245 Romain::dump_mem(instructionBuffer, 32, 24);
249 * -----------------------------------------------------
250 * Patch the target function to jump to our wrapper
251 * -----------------------------------------------------
253 DEBUG() << "Patching original code... (ibuf @ " << std::hex
254 << (void*)instructionBuffer << ")";
255 memset((void*)func_local, 0x90, bytes);
256 // 5 bytes: JMP [wrapper_func]
257 *((char*)func_local ) = 0xE9;
259 int offset = wrapper_address - orig_address - 5;
260 DEBUG() << "Offset: " << std::hex << wrapper_address
261 << " - " << orig_address << " = " << offset;
262 *(l4_addr_t*)(func_local + 1) = offset;
265 *((char*)func_local + 5) = 0x58;
267 DEBUG() << "Patched function entry: ";
268 Romain::dump_mem((char*)func_local, 32, 24);
277 * Observer responsible for making multithreaded lock acquisition
278 * deterministic. We achieve this by placing breakpoints on the
279 * following well-known pthread functions:
281 * __pthread_lock (internal pthread use)
282 * __pthread_unlock (internal pthread use)
285 * pthread_mutex_unlock
287 * Unsupported so far:
289 * pthread_mutex_destroy
290 * pthread_mutex_trylock
291 * pthread_mutex_timedlock
293 * Then we intercept all calls to these functions and emulate them
294 * ourselves. As the observer is only called after all replicas
295 * agreed to grab a lock, we are sure that we can immediately try
296 * to grab the lock and cannot see interfering replicas from other threads.
298 * However, we still have a race when two or more thread groups
299 * successfully decide to acquire the lock. We leave resolution of these
300 * issues to the underlying pthread implementation. The whole system's
301 * behavior nevertheless becomes deterministic, because we only need to
302 * make sure that for this single run we see the same ordering of lock
303 * acquisitions (and this is ensured by using the pthread lock).
305 class PThreadLock_priv : public Romain::PThreadLockObserver
307 LockFunction _functions[pt_max_wrappers];
308 std::map<l4_umword_t, PThreadMutex*> _locks;
309 Romain::App_model::Dataspace _lip_ds;
310 l4_addr_t _lip_local;
311 pthread_mutex_t _tablemtx;
313 unsigned /* Internal determinism counters */
314 det_lock_count, // counter: det_lock
315 det_unlock_count, // counter: det_unlock
316 /* External determinism counters: */
317 mtx_lock_count, // counter: pthread_mutex_lock
318 mtx_unlock_count, // counter: pthread_mutex_unlock
319 pt_lock_count, // counter: __pthread_lock
320 pt_unlock_count, // counter: __pthread_unlock
322 ignore_count, // counter: call ignored
323 total_count; // counter: total invocations
325 PThreadMutex* lookup_or_fail(unsigned addr)
327 pthread_mutex_lock(&_tablemtx);
328 PThreadMutex* r = _locks[addr];
330 ERROR() << "Called with uninitialized mutex?";
331 enter_kdebug("op on uninitialized mutex");
333 pthread_mutex_unlock(&_tablemtx);
338 PThreadMutex* lookup_or_create(unsigned addr, bool init_locked = false,
339 Romain::Thread_group* tg = 0)
341 pthread_mutex_lock(&_tablemtx);
342 PThreadMutex* mtx = _locks[addr];
344 mtx = new PThreadMutex(false);
351 pthread_mutex_unlock(&_tablemtx);
356 void det_lock(Romain::App_instance* inst,
357 Romain::App_thread* t,
358 Romain::Thread_group* tg,
359 Romain::App_model* am)
361 l4_addr_t stackaddr = am->rm()->remote_to_local(t->vcpu()->r()->sp, inst->id());
362 l4_addr_t lock = *(l4_addr_t*)(stackaddr + 4);
363 DEBUG() << "\033[35mLOCK @ \033[0m" << std::hex << lock;
364 lookup_or_create(lock, true, tg)->lock(tg);
365 //enter_kdebug("det_lock");
369 void det_unlock(Romain::App_instance* inst,
370 Romain::App_thread* t,
371 Romain::Thread_group* tg,
372 Romain::App_model* am)
374 l4_addr_t stackaddr = am->rm()->remote_to_local(t->vcpu()->r()->sp, inst->id());
375 l4_addr_t lock = *(l4_addr_t*)(stackaddr + 4);
376 DEBUG() << "\033[35mUNLOCK @ \033[0m" << std::hex << lock;
378 pthread_mutex_lock(&_tablemtx);
379 PThreadMutex* m = _locks[lock];
381 /* This may actually happen! The unlocker is simply faster sending the
382 * notification than the locker is in sending his wakeup. Hence,
383 * we need to potentially create the respective lock here.
385 l4_umword_t mtx_kind_ptr = am->rm()->remote_to_local(lock + 12, inst->id());
386 m = new PThreadMutex(*(l4_umword_t*)mtx_kind_ptr == PTHREAD_MUTEX_RECURSIVE_NP);
389 pthread_mutex_unlock(&_tablemtx);
391 //enter_kdebug("det_unlock");
395 void attach_lock_info_page(Romain::App_model *am);
400 : det_lock_count(0), det_unlock_count(0),
401 mtx_lock_count(0), mtx_unlock_count(0),
402 pt_lock_count(0), pt_unlock_count(0),
403 ignore_count(0), total_count(0)
405 pthread_mutex_init(&_tablemtx, 0);
407 _functions[mutex_init_id].configure("threads:mutex_init",
408 "threads:mutex_init_rep",
411 _functions[mutex_lock_id].configure("threads:mutex_lock",
412 "threads:mutex_lock_rep",
414 _functions[mutex_unlock_id].configure("threads:mutex_unlock",
415 "threads:mutex_unlock_rep",
418 _functions[pt_lock_id].configure("threads:lock",
421 _functions[pt_unlock_id].configure("threads:unlock",
422 "threads:unlock_rep",
427 DECLARE_OBSERVER("pthread_lock");
429 void lock(Romain::App_instance* inst, Romain::App_thread* thread,
430 Romain::Thread_group* group, Romain::App_model* model);
431 void unlock(Romain::App_instance* inst, Romain::App_thread* thread,
432 Romain::Thread_group* group, Romain::App_model* model);
434 void mutex_init(Romain::App_instance* inst, Romain::App_thread* thread,
435 Romain::Thread_group* group, Romain::App_model* model);
436 void mutex_lock(Romain::App_instance* inst, Romain::App_thread* thread,
437 Romain::Thread_group* group, Romain::App_model* model);
438 void mutex_unlock(Romain::App_instance* inst, Romain::App_thread* thread,
439 Romain::Thread_group* group, Romain::App_model* model);