From 34e0b29c00b0e400614298b089a38dbb5ec0d64a Mon Sep 17 00:00:00 2001 From: Faizan Alam <72894816+AlphaDaze@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:55:27 +0100 Subject: [PATCH 1/2] Add variable for parent to child data passthrough --- dbm.h | 3 +++ syscalls.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/dbm.h b/dbm.h index 07260e3a..dc9696f4 100644 --- a/dbm.h +++ b/dbm.h @@ -274,6 +274,9 @@ struct dbm_thread_s { int pending_signals[_NSIG]; uint32_t is_signal_pending; void *pstack; + + // Can be utilised to trasnfer data to child thread + void *shared_parent_data; }; typedef enum { diff --git a/syscalls.c b/syscalls.c index e8ae9f07..404615b8 100644 --- a/syscalls.c +++ b/syscalls.c @@ -126,6 +126,8 @@ dbm_thread *dbm_create_thread(dbm_thread *thread_data, void *next_inst, sys_clon new_thread_data->set_tid = set_tid; new_thread_data->clone_args = args; new_thread_data->pstack = pstack; + new_thread_data->shared_parent_data = thread_data->shared_parent_data; + pthread_attr_t attr; pthread_attr_init(&attr); From f588edd0b98d81201ace8aef37f678cb9ebbd286 Mon Sep 17 00:00:00 2001 From: Faizan Alam <72894816+AlphaDaze@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:56:46 +0100 Subject: [PATCH 2/2] Add data race detector plugin --- makefile | 8 + plugins/datarace/README.md | 45 +++ plugins/datarace/datarace.c | 435 +++++++++++++++++++++++++ plugins/datarace/datarace.h | 43 +++ plugins/datarace/detectors/detector.h | 128 ++++++++ plugins/datarace/detectors/djit.c | 336 +++++++++++++++++++ plugins/datarace/detectors/fasttrack.c | 416 +++++++++++++++++++++++ 7 files changed, 1411 insertions(+) create mode 100644 plugins/datarace/README.md create mode 100644 plugins/datarace/datarace.c create mode 100644 plugins/datarace/datarace.h create mode 100644 plugins/datarace/detectors/detector.h create mode 100644 plugins/datarace/detectors/djit.c create mode 100644 plugins/datarace/detectors/fasttrack.c diff --git a/makefile b/makefile index a3bc08bd..4cdc73e3 100644 --- a/makefile +++ b/makefile @@ -10,6 +10,8 @@ #PLUGINS+=plugins/memcheck/memcheck.S plugins/memcheck/memcheck.c plugins/memcheck/naive_stdlib.c #PLUGINS+=plugins/follow_exec.c #PLUGINS+=plugins/hotspot.c +#PLUGINS+=plugins/datarace/datarace.c plugins/datarace/detectors/fasttrack.c +#PLUGINS+=plugins/datarace/datarace.c plugins/datarace/detectors/djit.c OPTS= -DDBM_LINK_UNCOND_IMM OPTS+=-DDBM_INLINE_UNCOND_IMM @@ -87,6 +89,12 @@ pie: $(or $(OUTPUT_FILE),dbm): $(HEADERS) $(SOURCES) $(PLUGINS) $(CC) $(CFLAGS) $(LDFLAGS) $(OPTS) $(INCLUDES) -o $@ $(SOURCES) $(PLUGINS) $(PIE) $(LIBS) $(PLUGIN_ARGS) +datarace_ft: + PLUGINS="plugins/datarace/datarace.c plugins/datarace/detectors/fasttrack.c" CFLAGS="-DFASTTRACK" OUTPUT_FILE=mambo_datarace_ft make + +datarace_djit: + PLUGINS="plugins/datarace/datarace.c plugins/datarace/detectors/djit.c" CFLAGS="-DDJIT" OUTPUT_FILE=mambo_datarace_djit make + cachesim: PLUGINS="plugins/cachesim/cachesim.c plugins/cachesim/cachesim.S plugins/cachesim/cachesim_model.c" OUTPUT_FILE=mambo_cachesim make diff --git a/plugins/datarace/README.md b/plugins/datarace/README.md new file mode 100644 index 00000000..c759683a --- /dev/null +++ b/plugins/datarace/README.md @@ -0,0 +1,45 @@ +MAMBO datarace +============== + +This instrumentation plugin for [MAMBO](https://github.com/beehive-lab/mambo) detects possible data races with relatively low performance overhead for programs that utilise POSIX pthreads. This is still experimental software, please report any bugs using GitHub's [issue tracker](https://github.com/beehive-lab/mambo/issues). Any improvements and fixes are also welcome with a [pull request.](https://github.com/beehive-lab/mambo/pulls) + + +Building +--------- + + git clone https://github.com/beehive-lab/mambo.git + cd mambo + make datarace_ft + +Usage +------ + + +To run an application under MAMBO datarace, simply prefix the command with a call to `mambo_datarace_ft`. For example to execute `lscpu`, from the mambo source directory run: + + ./mambo_datarace_ft /usr/bin/lscpu + +> **Note:** Underlying programs must be compiled with the `-no-pie` flag (can be linked statically or dynamically) OR must be dynamically linked with library at /usr/ and mambo compiled with `NO_PIE_ENABLE` disabled in [datarace.h](datarace.h). See limitations at the bottom for more information. + + +Configuration +------------- + +#### FastTrack and Djit+ + +There are two separate algorithms to choose from for the datarace detector: [FastTrack](https://dl.acm.org/doi/abs/10.1145/1543135.1542490) and [DJIT+](https://dl.acm.org/doi/abs/10.1145/781498.781529). Both algorithms utilise the happens-before relationship. FastTrack is known to be an improvement over DJIT+ implementations by carrying out a happens-before comparison in O(1) for the majority of the cases compared to DJIT+'s O(n). + +> To compile and use the DJIT+ implementation, use `make datarace_djit` and run with `./mambo_datarace_djit`. + +#### Further configuration + +Further algorithms can be implemented using the handling functions for the various thread operations at the beginning of [datarace.c](datarace.c). Details of the implementation are also included at the top of the file for easier modification. + +[datarace.h](datarace.h) contains an option `no-pie` option enabled by default, to properly ignore libc. More debug information can also be enabled. + +Limitations +------------ + +The number of supported threads must be specified with `VC_INITIAL_SZ` in [detector.h](./detectors/detector.h) before compilation. It would be better to set this value to something like 4 or 8, and have it dynamically grow. This will require handling VC clocks of various sizes. Some work has been done for this feature in [fasttrack.c](./detectors/fasttrack.c) and commented out as a TODO to complete. + +Currently libc is ignored by not processing any reads/writes to addresses above `0x7000000000` which require the underlying program to be compiled with `no-pie`. A workaround involves dynamically linking to libc and not processing code within the /usr/ directory for data races. There is likely a better solution to this however. Please don't hesitate to open a [pull request](https://github.com/beehive-lab/mambo/pulls) for a fix or any other improvement. \ No newline at end of file diff --git a/plugins/datarace/datarace.c b/plugins/datarace/datarace.c new file mode 100644 index 00000000..e92db8b7 --- /dev/null +++ b/plugins/datarace/datarace.c @@ -0,0 +1,435 @@ +#ifdef PLUGINS_NEW + +#include +#include +#include +#include +#include + +#include "../../plugins.h" +#include "datarace.h" + + +/* + = DATARACE PLUGIN INFO + + - READ: + - WRITE: + - LOCK: - ADDR is to mutex object + - UNLOCK: - ADDR is to mutex object + - FORK(T, U) - T is parent tid, U is child tid + - JOIN(T, U) - T is parent tid, U is child tid + +Implementations: + - READ/WRITE: pre_inst - Note: Refer to how libc is ignored - either use -no-pie or dynamically compile + - LOCK: In pre_lock: Save the address of mutex. Commit lock in post_lock for that mutex + - UNLOCK: Commit unlock of the mutex in pre_unlock + - FORK: Commit in pre_thread by retrieving parent's TID + - JOIN: + - pre_clone: Get the pthread_t value of child thread (to be spawned) + - syscall.c::dbm_create_thread get pthread_t value from parent's thread data + - pre_thread: Get pthread_t of this thread and link it to its tid + - pre_join: Use pthread_t to get joining thread's tid + - post_join: Commit JOIN using joining tid + +TODO: + - JOIN: __clone() cb registered. Handle clone3 - replace with syscall cb? +*/ + + +/* + ## PROCESS EVENTS ## + Recorded operations are processed for data race detection. +*/ +thread_list_t *threads; +variable_list_t *variables; +lock_list_t *locks; + +// print symbol info when data race detected +void print_symbol_info(uintptr_t addr) { + char *sym_name, *filename; + void *symbol_start_addr; + + get_symbol_info_by_addr(addr, &sym_name, &symbol_start_addr, &filename); + + fprintf(stderr, ">>>> at %p: %s (%p) (%s)\n", (void*)addr, filename, + symbol_start_addr, (sym_name == NULL ? "none" : sym_name)); + + free(sym_name); + free(filename); +} + + +void process_fork(int parent_tid, int child_tid) { + info("--- FORK: Thread %d created by %d\n", child_tid, parent_tid); + + thread_t *child_thread = thread_list_smart_get(threads, child_tid); + if (parent_tid != 0) { // initial thread has no parent + thread_t *parent_thread = thread_list_smart_get(threads, parent_tid); + thread_fork(parent_thread, child_thread); + } +} + +void process_join(int parent_tid, int child_tid) { + info("--- JOIN: Thread %d joining %d\n", child_tid, parent_tid); + + thread_t *child_thread = thread_list_smart_get(threads, child_tid); + thread_t *parent_thread = thread_list_smart_get(threads, parent_tid); + thread_join(parent_thread, child_thread); +} + +void process_lock(int tid, uintptr_t addr) { + info("--- LOCK: Thread %d locked %p\n", tid, (void*)addr); + + thread_t *thread = thread_list_smart_get(threads, tid); + lock_t *lock = lock_list_smart_get(locks, addr); + lock_acquire(lock, thread); +} + +void process_unlock(int tid, uintptr_t addr) { + info("--- UNLOCK: Thread %d unlocked %p\n", tid, (void*)addr); + + thread_t *thread = thread_list_smart_get(threads, tid); + lock_t *lock = lock_list_smart_get(locks, addr); + lock_release(lock, thread); +} + +void process_read(int tid, uintptr_t ld_addr, uintptr_t inst_addr) { + debug("Processing: READ(%lx): %u %p\n", inst_addr, tid, ld_addr); + + thread_t *thread = thread_list_smart_get(threads, tid); + variable_t *var = variable_list_smart_get(variables, ld_addr); + if (!variable_update_read(var, thread)) { + fprintf(stderr, "!!! READ RACE: Possible read race - %p by thread %d\n", (void*)ld_addr, tid); + print_symbol_info(inst_addr); + } +} + +void process_write(int tid, uintptr_t st_addr, uintptr_t inst_addr) { + debug("Processing: WRITE(%lx): %u %p\n", inst_addr, tid, st_addr); + + thread_t *thread = thread_list_smart_get(threads, tid); + variable_t *var = variable_list_smart_get(variables, st_addr); + if (!variable_update_write(var, thread)) { + fprintf(stderr, "!!! WRITE RACE: Possible write race - %p by thread %d\n", (void*)st_addr, tid); + print_symbol_info(inst_addr); + } +} + + +/* + ## MAMBO PLUGIN ## + Track operations within underlying program. +*/ + +// join: map pthread_t value to tid of the same thread +mambo_ht_t *tid_map = NULL; + + +// ---------------- +// READ/WRITE: TID ADDR +// ---------------- +void handle_read_write(bool is_load, int tid, uintptr_t ld_st_addr, uintptr_t inst_addr) { + if (ld_st_addr == 0) return; + + if (is_load) { + process_read(tid, ld_st_addr, inst_addr); + } else { + process_write(tid, ld_st_addr, inst_addr); + } +} + +int ignore_usr_lib(uintptr_t addr) { + char *sym_name, *filename; + void *symbol_start_addr; + + get_symbol_info_by_addr(addr, &sym_name, &symbol_start_addr, &filename); + + // if filename starts with "/usr" then return true - ignore + if (filename != NULL && strncmp(filename, "/usr", 4) == 0) { + free(sym_name); + free(filename); + return 1; + } + + free(sym_name); + free(filename); + return 0; +} + +// capture load and store instructions +int datarace_pre_inst_handler(mambo_context *ctx) { + uintptr_t inst_addr = (uintptr_t)mambo_get_source_addr(ctx); + + +// ignore libc +#ifdef NO_PIE_ENABLE + // ignore libs. Underlying program must be compiled with -no-pie. + if (inst_addr >= (uintptr_t) 0x7000000000) { + return 0; + } +#else + // Use symbol data to ignore libc. libc must be located in /usr/. + if (ignore_usr_lib(inst_addr)) { + return 0; + } +#endif // NO_PIE_ENABLE + + + bool is_load = mambo_is_load(ctx); + bool is_store = mambo_is_store(ctx); + int tid = mambo_get_thread_id(ctx); // thread id + + if (is_load || is_store) { + emit_push(ctx, (1 << x0) | (1 << x1) | (1 << x2) | (1 << x3)); + + // calc address first! + mambo_calc_ld_st_addr(ctx, reg2); // put load/store address in reg2 + emit_set_reg(ctx, reg3, inst_addr); + emit_set_reg(ctx, reg1, tid); + emit_set_reg(ctx, reg0, is_load); // is load or store + emit_safe_fcall(ctx, handle_read_write, 4); + + emit_pop(ctx, (1 << x0) | (1 << x1) | (1 << x2) | (1 << x3)); + } + + return 0; +} + + +// ---------------- +// LOCK: TID ADDR +// ---------------- +void handle_lock(int tid, thread_data_t *thread_data) { + process_lock(tid, (uintptr_t)thread_data->mutex); // handle function required +} + +void lock_save_mutex(pthread_mutex_t *mutex, thread_data_t *thread_data) { + thread_data->mutex = mutex; // save mutex in pre lock +} + +// Capture the address of the lock that is to be acquired +int datarace_pre_pthread_lock(mambo_context *ctx) { + thread_data_t *thread_data = mambo_get_thread_plugin_data(ctx); + + emit_push(ctx, (1 << x0) | (1 << x1)); + + // reg0 holds address to mutex - int pthread_mutex_lock(pthread_mutex_t *mutex); + emit_set_reg(ctx, reg1, (uintptr_t)thread_data); + emit_safe_fcall(ctx, lock_save_mutex, 2); // Calls lock_save_mutex(*mutex, *thread_data) + + emit_pop(ctx, (1 << x0) | (1 << x1)); + + return 0; +} + +// The lock is successfully acquired after pthread_lock returns +int datarace_post_pthread_lock(mambo_context *ctx) { + thread_data_t *thread_data = mambo_get_thread_plugin_data(ctx); + + int tid = mambo_get_thread_id(ctx); // thread id + + emit_push(ctx, (1 << x0) | (1 << x1)); + + emit_set_reg(ctx, reg0, tid); + emit_set_reg(ctx, reg1, (uintptr_t)thread_data); + emit_safe_fcall(ctx, handle_lock, 2); // handle_lock(tid, thread_data) + + emit_pop(ctx, (1 << x0) | (1 << x1)); + + return 0; +} + +// ---------------- +// UNLOCK: TID ADDR +// ---------------- +int datarace_pre_pthread_unlock(mambo_context *ctx) { + int tid = mambo_get_thread_id(ctx); // thread id + + emit_push(ctx, (1 << x0) | (1 << x1)); + + emit_mov(ctx, reg1, reg0); // move mutex addr to reg1 + emit_set_reg(ctx, reg0, tid); + emit_safe_fcall(ctx, process_unlock, 3); + + emit_pop(ctx, (1 << x0) | (1 << x1)); + + return 0; +} + + +// ---------------- +// PTHREAD JOIN +// ---------------- +void save_child_pth(dbm_thread *dbm_thread_data, void *pass_pthread_t) { + /* Store child's pthread_t value in parent's dbm thread data. + The value is passed to the child when it's created. + The child thread copies this value when it spawns */ + dbm_thread_data->shared_parent_data = pass_pthread_t; +} + +int datarace_pre_clone(mambo_context *ctx) { + emit_push(ctx, (1 << x0) | (1 << x1) | (1 << x2) | (1 << x3)); + + // reg3 holds pthread_t value of the child thread to be spawned + emit_mov(ctx, reg1, reg3); + emit_set_reg(ctx, reg0, (uintptr_t) ctx->thread_data); + emit_safe_fcall(ctx, save_child_pth, 2); + + emit_pop(ctx, (1 << x0) | (1 << x1) | (1 << x2) | (1 << x3)); + + return 0; +} + + +void get_child_tid(void *joining_pthread_t, thread_data_t *thread_data) { + // get tid of joining thread using its pthread_t and store in thread_data + uintptr_t child_tid = 0; + mambo_ht_get(tid_map, (uintptr_t) joining_pthread_t, &child_tid); + thread_data->joining_tid = (int)child_tid; +} + +int datarace_pre_pthread_join(mambo_context *ctx) { + thread_data_t *thread_data = mambo_get_thread_plugin_data(ctx); + + emit_push(ctx, (1 << x0) | (1 << x1) | (1 << x2)); + + // get tid of the thread joining using its pthread_t value passed + // as first arg - save this tid to be used in post join + emit_set_reg(ctx, reg1, (uintptr_t) thread_data); + emit_safe_fcall(ctx, get_child_tid, 2); + + emit_pop(ctx, (1 << x0) | (1 << x1) | (1 << x2)); + + return 0; +} + +// handle function required +void handle_join(int parent_tid, thread_data_t *thread_data) { + // commit join completed + process_join(parent_tid, thread_data->joining_tid); +} + +// join operation complete when pthread_join returns +int datarace_post_pthread_join(mambo_context *ctx) { + thread_data_t *thread_data = mambo_get_thread_plugin_data(ctx); + int tid = mambo_get_thread_id(ctx); + + emit_push(ctx, (1 << x0) | (1 << x1)); + + emit_set_reg(ctx, reg0, tid); + emit_set_reg(ctx, reg1, (uintptr_t)thread_data); + emit_safe_fcall(ctx, handle_join, 2); + + emit_pop(ctx, (1 << x0) | (1 << x1)); + return 0; +} + +// ---------------- +// MAMBO THREAD (data + FORK + JOIN data) +// ---------------- +int datarace_pre_thread_handler(mambo_context *ctx) { + thread_data_t *thread_data = mambo_alloc(ctx, sizeof(thread_data_t)); + mambo_set_thread_plugin_data(ctx, thread_data); + + int tid = mambo_get_thread_id(ctx); + int parent_tid = mambo_get_parent_thread_id(ctx); + + // commit FORK(T, U) + process_fork(parent_tid, tid); + + // JOIN - get pthread_t value of current thread passed by the parent thread. + // This is the pthread_t value utilised by the underlying program. + // Since dbm captures pthread_create, it creates a new pthread_t value which + // cannot be related to the pthread_t value utilised by the underlying program. + void *curr_pthread_id = ctx->thread_data->shared_parent_data; + if (curr_pthread_id != NULL) { + // map pthread_t value to tid + int ret = mambo_ht_add(tid_map, (uintptr_t) curr_pthread_id, (uintptr_t) tid); + assert(ret == 0); + } + + return 0; +} + +int datarace_post_thread_handler(mambo_context *ctx) { + thread_data_t *thread_data = mambo_get_thread_plugin_data(ctx); + mambo_free(ctx, thread_data); + return 0; +} + +// ---------------- +// EXIT +// ---------------- +int datarace_exit_handler(mambo_context *ctx) { + // free data + mambo_free(ctx, threads); + mambo_free(ctx, locks); + mambo_free(ctx, variables); + mambo_free(ctx, tid_map); + return 0; +} + +__attribute__((constructor)) void datarace_trace_plugin() { + mambo_context *ctx = mambo_register_plugin(); + assert(ctx != NULL); + + int set_cb = MAMBO_INVALID_CB; + + threads = mambo_alloc(ctx, sizeof(thread_list_t)); + thread_list_init(threads); + locks = mambo_alloc(ctx, sizeof(lock_list_t)); + lock_list_init(locks); + variables = mambo_alloc(ctx, sizeof(variable_list_t)); + variable_list_init(variables); + + tid_map = mambo_alloc(ctx, sizeof(mambo_ht_t)); + set_cb = mambo_ht_init(tid_map, 16, 0, 70, true); + assert(set_cb == MAMBO_SUCCESS); + + + set_cb = mambo_register_exit_cb(ctx, &datarace_exit_handler); + assert(set_cb == MAMBO_SUCCESS); + + set_cb = mambo_register_pre_thread_cb(ctx, &datarace_pre_thread_handler); + assert(set_cb == MAMBO_SUCCESS); + + set_cb = mambo_register_post_thread_cb(ctx, &datarace_post_thread_handler); + assert(set_cb == MAMBO_SUCCESS); + + set_cb = mambo_register_pre_inst_cb(ctx, &datarace_pre_inst_handler); + assert(set_cb == MAMBO_SUCCESS); + + set_cb = mambo_register_function_cb(ctx, + "__clone", + &datarace_pre_clone, + NULL, + 4); + assert(set_cb == MAMBO_SUCCESS); + + + set_cb = mambo_register_function_cb(ctx, + "pthread_mutex_lock", + &datarace_pre_pthread_lock, + &datarace_post_pthread_lock, + 2); + + assert(set_cb == MAMBO_SUCCESS); + + set_cb = mambo_register_function_cb(ctx, + "pthread_mutex_unlock", + &datarace_pre_pthread_unlock, + NULL, + 2); + assert(set_cb == MAMBO_SUCCESS); + + set_cb = mambo_register_function_cb(ctx, + "pthread_join", + &datarace_pre_pthread_join, + &datarace_post_pthread_join, + 2); + +} + + +#endif // PLUGINS_NEW diff --git a/plugins/datarace/datarace.h b/plugins/datarace/datarace.h new file mode 100644 index 00000000..a683e271 --- /dev/null +++ b/plugins/datarace/datarace.h @@ -0,0 +1,43 @@ +#ifndef DATARACE_H +#define DATARACE_H + +#include "detectors/detector.h" + +#include +#include + +#if !defined(FASTTRACK) && !defined(DJIT) + #error "Neither DJIT nor FASTTRACK is defined" +#endif + + +/* + Define this if exe run under mambo is compiled with -no-pie. (Recommended) + Otherwise exe must be dynamically linked with libs at /usr/. + Required to ignore data race detection in libc. +*/ +#define NO_PIE_ENABLE + +/* + Enable for more verbose information. (Recommended) + Prints fork, join, lock, unlock operations for easier debugging of races. +*/ +#define INFO +#ifdef INFO + #define info(...) fprintf(stderr, __VA_ARGS__) +#else + #define info(...) +#endif + +#ifdef DEBUG + #define debug(...) fprintf(stderr, __VA_ARGS__) +#else + #define debug(...) +#endif + +typedef struct thread_data { + pthread_mutex_t *mutex; + uint64_t joining_tid; // tid associated with pthread_t in pre join +} thread_data_t; + +#endif // DATARACE_H diff --git a/plugins/datarace/detectors/detector.h b/plugins/datarace/detectors/detector.h new file mode 100644 index 00000000..0e7689c2 --- /dev/null +++ b/plugins/datarace/detectors/detector.h @@ -0,0 +1,128 @@ +#ifndef DETECTOR_H +#define DETECTOR_H + +#include +#include +#include +#include "../../../api/hash_table.h" + +// Currently support up to VC_INITIAL_SZ threads +#define VC_INITIAL_SZ 8 + +// TODO: Add destructors/free functions + +// VECTOR CLOCK // +typedef struct { + int *clocks; + size_t size; + pthread_mutex_t lock; +} vector_clock_t; + +// EPOCH // +#ifdef FASTTRACK +typedef struct { + int clock; + int thread_idx; + pthread_mutex_t lock; +} epoch_t; +#endif // EPOCH + +int vector_clock_init(vector_clock_t *vc, size_t size); +int vector_clock_get(vector_clock_t *vc, int idx); +void vector_clock_increment(vector_clock_t *vc, int idx); +void vector_clock_set(vector_clock_t *vc, int idx, int clock); +int vector_clock_update(vector_clock_t *vc, vector_clock_t *other_vc); +int vector_clock_happens_before(vector_clock_t *vc, vector_clock_t *other_vc); + + +// THREAD // +typedef struct { + size_t idx; + vector_clock_t *vc; +} thread_t; + +int thread_init(thread_t *t, int thread_idx); +int thread_get_clock(thread_t *t); +int thread_get_idx(thread_t *t); +void thread_increment(thread_t *t); +void thread_update_vc(thread_t *t, vector_clock_t *other_clock); +void thread_fork(thread_t *parent_thread, thread_t *child_thread); +void thread_join(thread_t *parent_thread, thread_t *child_thread); + + +// LOCK // +typedef struct { + vector_clock_t *vc; +} lock_t; + +int lock_init(lock_t *l); +void lock_acquire(lock_t *lock, thread_t *thread); +void lock_release(lock_t *lock, thread_t *thread); + + +// VARIABLE // +#if defined(FASTTRACK) +typedef struct { + vector_clock_t *rx_vc; + epoch_t *rx_epoch; + epoch_t *wx_epoch; + bool is_shared; +} variable_t; + +#elif defined(DJIT) +typedef struct { + vector_clock_t *rx; + vector_clock_t *wx; +} variable_t; + +#endif // VARIABLE + +int variable_init(variable_t *v); +int variable_read_is_race_free(variable_t *v, vector_clock_t *thread_clock); +int variable_write_is_race_free(variable_t *v, vector_clock_t *thread_clock); +int variable_update_read(variable_t *v, thread_t *thread); +int variable_update_write(variable_t *v, thread_t *thread); + + +// Lists +// ThreadList +typedef struct { + thread_t **threads; // array of threads + size_t size; + size_t capacity; + mambo_ht_t *tid_index_ht; // tid -> index ht + pthread_mutex_t lock; +} thread_list_t; + +typedef struct { + lock_t **locks; + size_t size; + size_t capacity; + mambo_ht_t *addr_index_ht; // address -> index ht + pthread_mutex_t lock; +} lock_list_t; + +typedef struct { + variable_t **variables; + size_t size; + size_t capacity; + mambo_ht_t *addr_index_ht; // address -> index ht + pthread_mutex_t lock; +} variable_list_t; + +// smart_get: return if item in list, else adds it and returns it + +int thread_list_init(thread_list_t *list); +thread_t *thread_list_append(thread_list_t *list, uint64_t tid); +thread_t *thread_list_smart_get(thread_list_t *list, uint64_t tid); + +int lock_list_init(lock_list_t *list); +lock_t *lock_list_append(lock_list_t *list, uintptr_t addr); +lock_t *lock_list_smart_get(lock_list_t *list, uintptr_t addr); + +int variable_list_init(variable_list_t *list); +variable_t *variable_list_append(variable_list_t *list, uintptr_t addr); +variable_t *variable_list_smart_get(variable_list_t *list, uintptr_t addr); + + +#endif // DETECTOR_H diff --git a/plugins/datarace/detectors/djit.c b/plugins/datarace/detectors/djit.c new file mode 100644 index 00000000..4e8f5e5f --- /dev/null +++ b/plugins/datarace/detectors/djit.c @@ -0,0 +1,336 @@ +#include "detector.h" + +#include +#include + +// VECTOR CLOCK // +int vector_clock_init(vector_clock_t *vc, size_t size) { + pthread_mutex_init(&vc->lock, NULL); + vc->size = size; + + vc->clocks = calloc(size, sizeof(int)); + if (vc->clocks == NULL) return -1; + + return 1; +} + +int vector_clock_get(vector_clock_t *vc, int idx) { + if (idx >= 0 && idx < vc->size) { + return vc->clocks[idx]; + } + + return -1; +} + +void vector_clock_increment(vector_clock_t *vc, int idx) { + pthread_mutex_lock(&vc->lock); + assert(idx >= 0); + assert(idx < vc->size); + + ++vc->clocks[idx]; + pthread_mutex_unlock(&vc->lock); +} + +void vector_clock_set(vector_clock_t *vc, int idx, int clock) { + pthread_mutex_lock(&vc->lock); + + vc->clocks[idx] = clock; + pthread_mutex_unlock(&vc->lock); +} + +int vector_clock_update(vector_clock_t *vc, vector_clock_t *other_vc) { + pthread_mutex_lock(&vc->lock); + + // vc[i] = max(vc[i], other_vc[i]) + for (size_t i = 0; i < other_vc->size; ++i) { + if (vc->clocks[i] < other_vc->clocks[i]) { + vc->clocks[i] = other_vc->clocks[i]; + } + } + + pthread_mutex_unlock(&vc->lock); + return 1; +} + +int vector_clock_happens_before(vector_clock_t *vc, vector_clock_t *other_vc) { + pthread_mutex_lock(&vc->lock); + pthread_mutex_lock(&other_vc->lock); + int min_size = (vc->size < other_vc->size) ? vc->size : other_vc->size; + + for (int i = 0; i < min_size; ++i) { + if (vc->clocks[i] > other_vc->clocks[i]) { + pthread_mutex_unlock(&vc->lock); + pthread_mutex_unlock(&other_vc->lock); + return 0; // false + } + } + + pthread_mutex_unlock(&vc->lock); + pthread_mutex_unlock(&other_vc->lock); + return 1; // true +} + + +// THREAD // +int thread_init(thread_t *t, int thread_idx) { + t->idx = thread_idx; + + t->vc = malloc(sizeof(vector_clock_t)); + if (t->vc == NULL) + return -1; + if (vector_clock_init(t->vc, VC_INITIAL_SZ) == -1) + return -1; + + vector_clock_increment(t->vc, t->idx); + + return 1; +} + +int thread_get_clock(thread_t *t) { + return vector_clock_get(t->vc, t->idx); +} + +int thread_get_idx(thread_t *t) { + return t->idx; +} + +void thread_increment(thread_t *t) { + vector_clock_increment(t->vc, t->idx); +} +void thread_update_vc(thread_t *t, vector_clock_t *other_clock) { + vector_clock_update(t->vc, other_clock); +} + +void thread_fork(thread_t *parent_thread, thread_t *child_thread) { + // Cu = Cu join Ct + // Ct += 1 + thread_update_vc(child_thread, parent_thread->vc); + thread_increment(parent_thread); +} + +void thread_join(thread_t *parent_thread, thread_t *child_thread) { + thread_update_vc(parent_thread, child_thread->vc); + thread_increment(child_thread); +} + +// LOCK // +int lock_init(lock_t *l) { + l->vc = malloc(sizeof(vector_clock_t)); + if (l->vc == NULL) + return -1; + if (vector_clock_init(l->vc, VC_INITIAL_SZ) == -1) { + free(l->vc); + return -1; + } + + return 1; +} + +void lock_acquire(lock_t *l, thread_t *t) { + thread_update_vc(t, l->vc); +} +void lock_release(lock_t *l, thread_t *t) { + vector_clock_update(l->vc, t->vc); + thread_increment(t); +} + + +// VARIABLE // +int variable_init(variable_t *v) { + v->rx = malloc(sizeof(vector_clock_t)); + if (v->rx == NULL) + return -1; + if (vector_clock_init(v->rx, VC_INITIAL_SZ) == -1) { + free(v->rx); + return -1; + } + + v->wx = malloc(sizeof(vector_clock_t)); + if (v->wx == NULL) { + free(v->rx); + return -1; + } + if (vector_clock_init(v->wx, VC_INITIAL_SZ) == -1) { + free(v->rx); + free(v->wx); + return -1; + } + + return 1; +} + +int variable_read_is_race_free(variable_t *v, vector_clock_t *thread_clock) { + return vector_clock_happens_before(v->wx, thread_clock); +} + +int variable_write_is_race_free(variable_t *v, vector_clock_t *thread_clock) { + return vector_clock_happens_before(v->wx, thread_clock) && + vector_clock_happens_before(v->rx, thread_clock); +} + +int variable_update_read(variable_t *v, thread_t *t) { + if (variable_read_is_race_free(v, t->vc)) { + vector_clock_set(v->rx, t->idx, thread_get_clock(t)); + return 1; // true + } + return 0; // false +} + +int variable_update_write(variable_t *v, thread_t *t) { + if (variable_write_is_race_free(v, t->vc)) { + vector_clock_set(v->wx, t->idx, thread_get_clock(t)); + return 1; // true + } + return 0; // false +} + + +int thread_list_init(thread_list_t *list) { + pthread_mutex_init(&list->lock, NULL); + + list->size = 0; + list->capacity = VC_INITIAL_SZ; + + list->threads = malloc(list->capacity * sizeof(thread_t *)); + for (int i = 0; i < list->capacity; ++i) { + list->threads[i] = NULL; + } + + list->tid_index_ht = malloc(sizeof(mambo_ht_t)); + mambo_ht_init(list->tid_index_ht, VC_INITIAL_SZ, 0, 90, true); + + return 1; +} + +thread_t *thread_list_append(thread_list_t *list, uint64_t tid) { + if (list->size == list->capacity) { + list->capacity *= 2; + list->threads = realloc(list->threads, list->capacity * sizeof(thread_t *)); + } + + // create new thread + thread_t *new_thread = malloc(sizeof(thread_t)); + thread_init(new_thread, list->size); + + // link tid to index + mambo_ht_add(list->tid_index_ht, tid, list->size); + list->threads[list->size] = new_thread; + + ++list->size; + return new_thread; +} + +thread_t *thread_list_smart_get(thread_list_t *list, uint64_t tid) { + pthread_mutex_lock(&list->lock); + + uint64_t index = -1; + int ret = mambo_ht_get(list->tid_index_ht, tid, &index); + + if (ret == -1) { + pthread_mutex_unlock(&list->lock); + return thread_list_append(list, tid); + } + + pthread_mutex_unlock(&list->lock); + return list->threads[index]; +} + + +int lock_list_init(lock_list_t *list) { + pthread_mutex_init(&list->lock, NULL); + + list->size = 0; + list->capacity = VC_INITIAL_SZ; + + list->locks = malloc(list->capacity * sizeof(lock_t *)); + for (int i = 0; i < list->capacity; ++i) { + list->locks[i] = NULL; + } + + list->addr_index_ht = malloc(sizeof(mambo_ht_t)); + mambo_ht_init(list->addr_index_ht, VC_INITIAL_SZ, 0, 90, true); + + return 1; +} + +lock_t *lock_list_append(lock_list_t *list, uint64_t tid) { + if (list->size == list->capacity) { + list->capacity *= 2; + list->locks = realloc(list->locks, list->capacity * sizeof(lock_t *)); + } + // create new thread + lock_t *new_lock = malloc(sizeof(lock_t)); + lock_init(new_lock); + + // link tid to index + mambo_ht_add(list->addr_index_ht, tid, list->size); + list->locks[list->size] = new_lock; + + ++list->size; + return new_lock; +} + +lock_t *lock_list_smart_get(lock_list_t *list, uint64_t tid) { + pthread_mutex_lock(&list->lock); + + uint64_t index = -1; + int ret = mambo_ht_get(list->addr_index_ht, tid, &index); + + if (ret == -1) { + pthread_mutex_unlock(&list->lock); + return lock_list_append(list, tid); + } + + pthread_mutex_unlock(&list->lock); + return list->locks[index]; +} + +int variable_list_init(variable_list_t *list) { + pthread_mutex_init(&list->lock, NULL); + + list->size = 0; + list->capacity = VC_INITIAL_SZ; + + list->variables = malloc(list->capacity * sizeof(variable_t *)); + for (int i = 0; i < list->capacity; ++i) { + list->variables[i] = NULL; + } + + list->addr_index_ht = malloc(sizeof(mambo_ht_t)); + mambo_ht_init(list->addr_index_ht, VC_INITIAL_SZ, 0, 90, true); + + return 1; +} + +variable_t *variable_list_append(variable_list_t *list, uint64_t tid) { + if (list->size == list->capacity) { + list->capacity *= 2; + list->variables = realloc(list->variables, list->capacity * sizeof(variable_t *)); + } + // create new thread + variable_t *new_variable = malloc(sizeof(variable_t)); + variable_init(new_variable); + + // link tid to index + mambo_ht_add(list->addr_index_ht, tid, list->size); + list->variables[list->size] = new_variable; + + ++list->size; + return new_variable; +} + +variable_t *variable_list_smart_get(variable_list_t *list, uint64_t tid) { + pthread_mutex_lock(&list->lock); + + uint64_t index = -1; + int ret = mambo_ht_get(list->addr_index_ht, tid, &index); + + if (ret == -1) { + pthread_mutex_unlock(&list->lock); + return variable_list_append(list, tid); + } + + pthread_mutex_unlock(&list->lock); + return list->variables[index]; +} + diff --git a/plugins/datarace/detectors/fasttrack.c b/plugins/datarace/detectors/fasttrack.c new file mode 100644 index 00000000..4ed00d07 --- /dev/null +++ b/plugins/datarace/detectors/fasttrack.c @@ -0,0 +1,416 @@ +#include "detector.h" + +#include +#include + + +// VECTOR CLOCK // +int vector_clock_init(vector_clock_t *vc, size_t size) { + pthread_mutex_init(&vc->lock, NULL); + vc->size = size; + + vc->clocks = calloc(size, sizeof(int)); + if (vc->clocks == NULL) return -1; + + return 1; +} + +int vector_clock_get(vector_clock_t *vc, int idx) { + if (idx >= 0 && idx < vc->size) { + return vc->clocks[idx]; + } + + return -1; +} + +void vector_clock_increment(vector_clock_t *vc, int idx) { + pthread_mutex_lock(&vc->lock); + assert(idx >= 0); + assert(idx < vc->size); + + ++vc->clocks[idx]; + pthread_mutex_unlock(&vc->lock); +} + +void vector_clock_set(vector_clock_t *vc, int idx, int clock) { + pthread_mutex_lock(&vc->lock); + assert(idx >= 0); + assert(idx < vc->size); + + vc->clocks[idx] = clock; + pthread_mutex_unlock(&vc->lock); +} + + +int vector_clock_update(vector_clock_t *vc, vector_clock_t *other_vc) { + pthread_mutex_lock(&vc->lock); + + // vc[i] = max(vc[i], other_vc[i]) + for (size_t i = 0; i < other_vc->size; ++i) { + if (vc->clocks[i] < other_vc->clocks[i]) { + vc->clocks[i] = other_vc->clocks[i]; + } + } + + pthread_mutex_unlock(&vc->lock); + return 1; +} + +int vector_clock_happens_before(vector_clock_t *vc, vector_clock_t *other_vc) { + pthread_mutex_lock(&vc->lock); + pthread_mutex_lock(&other_vc->lock); + int min_size = (vc->size < other_vc->size) ? vc->size : other_vc->size; + + for (int i = 0; i < min_size; ++i) { + if (vc->clocks[i] > other_vc->clocks[i]) { + pthread_mutex_unlock(&vc->lock); + pthread_mutex_unlock(&other_vc->lock); + return 0; // false + } + } + + // TODO: Dynamic number of threads feature + // check remaining clocks in vc + // if (min_size < vc->size) { + // for (int i = min_size; i < vc->size; ++i) { + // if (vc->clocks[i] > 0) { + // return 0; // false + // } + // } + // } + + pthread_mutex_unlock(&vc->lock); + pthread_mutex_unlock(&other_vc->lock); + return 1; // true +} + +// EPOCH // +int epoch_init(epoch_t *epoch) { + pthread_mutex_init(&epoch->lock, NULL); + epoch->clock = 0; + epoch->thread_idx = 0; + return 1; +} + +void epoch_free(epoch_t *epoch) { + if (epoch != NULL) { + free(epoch); + epoch = NULL; + } +} + +void epoch_set(epoch_t *epoch, int thread_id, int clock) { + pthread_mutex_lock(&epoch->lock); + + if (epoch != NULL) { + epoch->clock = clock; + epoch->thread_idx = thread_id; + } + pthread_mutex_unlock(&epoch->lock); +} + +// O(1) operation +int epoch_happens_before_vc(epoch_t *epoch, vector_clock_t *vc) { + pthread_mutex_lock(&epoch->lock); + + if (epoch == NULL || vc == NULL || epoch->thread_idx >= vc->size) { + pthread_mutex_unlock(&epoch->lock); + return -1; + } + + pthread_mutex_unlock(&epoch->lock); + // c@t happens before V iff c <= V(t) + return epoch->clock <= vector_clock_get(vc, epoch->thread_idx); +} + + +// THREAD // +int thread_init(thread_t *t, int thread_idx) { + t->idx = thread_idx; + + t->vc = malloc(sizeof(vector_clock_t)); + if (t->vc == NULL) + return -1; + if (vector_clock_init(t->vc, VC_INITIAL_SZ) == -1) + return -1; + + vector_clock_increment(t->vc, t->idx); + + return 1; +} + +int thread_get_clock(thread_t *t) { + return vector_clock_get(t->vc, t->idx); +} + +int thread_get_idx(thread_t *t) { + return t->idx; +} + +void thread_increment(thread_t *t) { + vector_clock_increment(t->vc, t->idx); +} +void thread_update_vc(thread_t *t, vector_clock_t *other_clock) { + vector_clock_update(t->vc, other_clock); +} + +void thread_fork(thread_t *parent_thread, thread_t *child_thread) { + // Cu = Cu join Ct + // Ct += 1 + thread_update_vc(child_thread, parent_thread->vc); + thread_increment(parent_thread); +} + +void thread_join(thread_t *parent_thread, thread_t *child_thread) { + thread_update_vc(parent_thread, child_thread->vc); + thread_increment(child_thread); +} + +// LOCK // +int lock_init(lock_t *l) { + l->vc = malloc(sizeof(vector_clock_t)); + if (l->vc == NULL) + return -1; + if (vector_clock_init(l->vc, VC_INITIAL_SZ) == -1) { + free(l->vc); + return -1; + } + + return 1; +} + + +void lock_acquire(lock_t *l, thread_t *t) { + thread_update_vc(t, l->vc); +} +void lock_release(lock_t *l, thread_t *t) { + vector_clock_update(l->vc, t->vc); + thread_increment(t); +} + + +// VARIABLE // +int variable_init(variable_t *v) { + v->rx_vc = malloc(sizeof(vector_clock_t)); + vector_clock_init(v->rx_vc, VC_INITIAL_SZ); + + v->rx_epoch = malloc(sizeof(epoch_t)); + epoch_init(v->rx_epoch); + v->wx_epoch = malloc(sizeof(epoch_t)); + epoch_init(v->wx_epoch); + + return 1; +} + +int variable_update_read(variable_t *v, thread_t *t) { + // var.read_epoch == thread.epoch - same read + if (v->rx_epoch->thread_idx == t->idx + && v->rx_epoch->clock == thread_get_clock(t)) { + return 1; // success + } + + // read-shared and same clock as last read + if (v->is_shared == true + && vector_clock_get(v->rx_vc, t->idx) == thread_get_clock(t)) { + return 1; // success + } + + // var.write_epoch > thread->epoch + if (!epoch_happens_before_vc(v->wx_epoch, t->vc)) { + return 0; // ERROR Write-Read Race + } + + if (v->is_shared == true) { + vector_clock_set(v->rx_vc, t->idx, thread_get_clock(t)); + } else { + if (epoch_happens_before_vc(v->rx_epoch, t->vc)) { + // set read epoch to thread.epoch + epoch_set(v->rx_epoch, t->idx, thread_get_clock(t)); + } else { + // store epoch in vc and update vc to thread vc + vector_clock_set(v->rx_vc, v->rx_epoch->thread_idx, v->rx_epoch->clock); + vector_clock_set(v->rx_vc, t->idx, thread_get_clock(t)); + v->is_shared = true; + } + } + + return 1; // success +} + +int variable_update_write(variable_t *v, thread_t *t) { + // var.write_epoch == thread.epoch + if (v->wx_epoch->thread_idx == t->idx + && v->wx_epoch->clock == thread_get_clock(t)) { + return 1; // success + } + + // var.write_epoch > thread->epoch + if (!epoch_happens_before_vc(v->wx_epoch, t->vc)) { + return 0; // ERROR Write-Write Race + } + + if (!v->is_shared) { + if (!epoch_happens_before_vc(v->rx_epoch, t->vc)) { + return 0; // ERROR Read-Write race + } + } else { + if (!vector_clock_happens_before(v->rx_vc, t->vc)) { + return 0; // ERROR Read-Write race + } + } + + // set write epoch to thread.epoch + epoch_set(v->wx_epoch, t->idx, thread_get_clock(t)); + return 1; // Success +} + + +// TODO: add error checking +int thread_list_init(thread_list_t *list) { + pthread_mutex_init(&list->lock, NULL); + + list->size = 0; + list->capacity = VC_INITIAL_SZ; + + list->threads = malloc(list->capacity * sizeof(thread_t *)); + for (int i = 0; i < list->capacity; ++i) { + list->threads[i] = NULL; + } + + list->tid_index_ht = malloc(sizeof(mambo_ht_t)); + mambo_ht_init(list->tid_index_ht, VC_INITIAL_SZ, 0, 90, true); + + return 1; +} + +thread_t *thread_list_append(thread_list_t *list, uint64_t tid) { + if (list->size == list->capacity) { + list->capacity *= 2; + list->threads = realloc(list->threads, list->capacity * sizeof(thread_t *)); + } + // create new thread + thread_t *new_thread = malloc(sizeof(thread_t)); + thread_init(new_thread, list->size); + + // link tid to index + mambo_ht_add(list->tid_index_ht, tid, list->size); + list->threads[list->size] = new_thread; + + ++list->size; + return new_thread; +} + +thread_t *thread_list_smart_get(thread_list_t *list, uint64_t tid) { + pthread_mutex_lock(&list->lock); + + uint64_t index = -1; + int ret = mambo_ht_get(list->tid_index_ht, tid, &index); + + if (ret == -1) { + pthread_mutex_unlock(&list->lock); + return thread_list_append(list, tid); + } + + pthread_mutex_unlock(&list->lock); + return list->threads[index]; +} + + +int lock_list_init(lock_list_t *list) { + pthread_mutex_init(&list->lock, NULL); + + list->size = 0; + list->capacity = VC_INITIAL_SZ; + + list->locks = malloc(list->capacity * sizeof(lock_t *)); + for (int i = 0; i < list->capacity; ++i) { + list->locks[i] = NULL; + } + + list->addr_index_ht = malloc(sizeof(mambo_ht_t)); + mambo_ht_init(list->addr_index_ht, VC_INITIAL_SZ, 0, 90, true); + + return 1; +} + +lock_t *lock_list_append(lock_list_t *list, uint64_t tid) { + if (list->size == list->capacity) { + list->capacity *= 2; + list->locks = realloc(list->locks, list->capacity * sizeof(lock_t *)); + } + // create new thread + lock_t *new_lock = malloc(sizeof(lock_t)); + lock_init(new_lock); + + // link tid to index + mambo_ht_add(list->addr_index_ht, tid, list->size); + list->locks[list->size] = new_lock; + + ++list->size; + return new_lock; +} + +lock_t *lock_list_smart_get(lock_list_t *list, uint64_t tid) { + pthread_mutex_lock(&list->lock); + + uint64_t index = -1; + int ret = mambo_ht_get(list->addr_index_ht, tid, &index); + + if (ret == -1) { + pthread_mutex_unlock(&list->lock); + return lock_list_append(list, tid); + } + + pthread_mutex_unlock(&list->lock); + return list->locks[index]; +} + +int variable_list_init(variable_list_t *list) { + pthread_mutex_init(&list->lock, NULL); + + list->size = 0; + list->capacity = VC_INITIAL_SZ; + + list->variables = malloc(list->capacity * sizeof(variable_t *)); + for (int i = 0; i < list->capacity; ++i) { + list->variables[i] = NULL; + } + + list->addr_index_ht = malloc(sizeof(mambo_ht_t)); + mambo_ht_init(list->addr_index_ht, VC_INITIAL_SZ, 0, 90, true); + + return 1; +} + +variable_t *variable_list_append(variable_list_t *list, uint64_t tid) { + if (list->size == list->capacity) { + list->capacity *= 2; + list->variables = realloc(list->variables, list->capacity * sizeof(variable_t *)); + } + // create new thread + variable_t *new_variable = malloc(sizeof(variable_t)); + variable_init(new_variable); + + // link tid to index + mambo_ht_add(list->addr_index_ht, tid, list->size); + list->variables[list->size] = new_variable; + + ++list->size; + return new_variable; +} + +variable_t *variable_list_smart_get(variable_list_t *list, uint64_t tid) { + pthread_mutex_lock(&list->lock); + + uint64_t index = -1; + int ret = mambo_ht_get(list->addr_index_ht, tid, &index); + + if (ret == -1) { + pthread_mutex_unlock(&list->lock); + return variable_list_append(list, tid); + } + + pthread_mutex_unlock(&list->lock); + return list->variables[index]; +} +