Skip to content

Commit ad3e7a7

Browse files
committed
completing first working version of the race detector
1 parent dcace0f commit ad3e7a7

File tree

5 files changed

+80
-70
lines changed

5 files changed

+80
-70
lines changed

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
project(sample)
2-
add_library(myclient SHARED instrument.c error_detector.c)
2+
add_library(myclient SHARED instrument.c race_detector.c)
33
target_include_directories(myclient PRIVATE ${include/})
44
find_package(DynamoRIO PATHS ../Libs/DynamoRIO-AArch64-Linux-9.0.1/cmake)
55
if (NOT DynamoRIO_FOUND)

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Basic Thread Error Detector based on DynamoRIO
22

3-
The project is split into `instrument.c` which only contains purely technical (memory instrumentation, fn wrapping..) DynamorRIO api setup/config and `error_detector.c` which contains the race detector implementation described in [this](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/35604.pdf) paper. All events are "exported" in instrument.h and implemented by error_detector.c (fed to DynamoRIO by `instrument.c`).
3+
The project is split into `instrument.c` which only contains purely technical (memory instrumentation, fn wrapping..) DynamorRIO api setup/config and `race_detector.c` which contains the race detector implementation described in [this](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/35604.pdf) paper. All events are "exported" in instrument.h and implemented by race_detector.c (fed to DynamoRIO by `instrument.c`).
44

55
The detector is fully build on a clock-vector before/after relations mechanism to detect races.
66

instrument.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ module_load_event(void *drcontext, const module_data_t *mod, bool loaded)
5656
DR_ASSERT(ok);
5757
}
5858
size_t modoffs_malloc;
59-
drsym_error_t sym_res_malloc = drsym_lookup_symbol(mod->full_path, "malloc", &modoffs_malloc, DRSYM_DEMANGLE);
59+
drsym_error_t sym_res_malloc = drsym_lookup_symbol(mod->full_path, "detector_malloc", &modoffs_malloc, DRSYM_DEMANGLE);
6060
if (sym_res_malloc == DRSYM_SUCCESS) {
6161
app_pc towrap_malloc = mod->start + modoffs_malloc;
6262
bool ok = drwrap_wrap(towrap_malloc, wrap_pre_malloc, wrap_post_malloc);

error_detector.c race_detector.c

+70-64
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44
#define MAX_ALLOCS 10000
55
const u64 linear_set_size_increment = 1000000;
66

7+
8+
9+
typedef enum LockWritability {
10+
WriteHeld,
11+
ReadHeld,
12+
} LockWritability;
13+
14+
typedef struct LockAccess {
15+
LockWritability state;
16+
usize addr;
17+
u64 callee_thread_id;
18+
} LockAccess;
19+
20+
721
// todo => shrink to 8 bytes. Should be possible if memory address access/accessing only store
822
// bytes of the virtual address that define the memory locations relative to the process pages)
923
// reference: https://developer.arm.com/documentation/den0024/a/The-Memory-Management-Unit/Translating-a-Virtual-Address-to-a-Physical-Address
@@ -14,12 +28,10 @@ typedef struct MemoryAccess {
1428
u64 size;
1529
u64 callee_thread_id;
1630
u64 memory_access_count;
31+
u32 has_lock;
32+
LockAccess lock_access;
1733
} MemoryAccess;
1834

19-
typedef enum LockWritability {
20-
WriteHeld,
21-
ReadHeld,
22-
} LockWritability;
2335

2436
typedef struct LockState {
2537
LockWritability state;
@@ -30,13 +42,6 @@ typedef struct LockState {
3042
} LockState;
3143

3244

33-
typedef struct LockAccess {
34-
LockWritability state;
35-
usize addr;
36-
u64 callee_thread_id;
37-
u64 memory_access_count;
38-
} LockAccess;
39-
4045
typedef struct MemoryAllocation {
4146
usize addr;
4247
u64 size;
@@ -54,20 +59,16 @@ typedef struct ThreadState {
5459
u64 mem_write_set_capacity;
5560
u64 mem_write_set_len;
5661

57-
5862
LockState *lock_state_set;
5963
u64 lock_state_set_capacity;
6064
u64 lock_state_set_len;
6165

62-
LockAccess *lock_write_held_set;
63-
u64 lock_write_held_set_capacity;
64-
u64 lock_write_held_set_len;
65-
66-
LockAccess *lock_read_held_set;
67-
u64 lock_read_held_set_capacity;
68-
u64 lock_read_held_set_len;
66+
usize last_locked_mutex_addr;
6967
} ThreadState;
7068

69+
usize debug_ok_checks = 0;
70+
usize debug_error_checks = 0;
71+
7172

7273
// max allocations is seperate from thread state since it needs to be itreated thorugh on every error check
7374
MemoryAllocation allocations[MAX_ALLOCS] = {};
@@ -95,15 +96,15 @@ i64 find_thread_by_tid(u64 tid) {
9596
return -1;
9697
}
9798

98-
i64 find_lockset_by_memory_access_count(LockAccess *lock_set, u64 lock_set_len, u64 access_count) {
99-
u64 i;
100-
for (i = 0; i <= lock_set_len; i++) {
101-
if (lock_set[i].memory_access_count == access_count) {
102-
return i;
103-
}
104-
}
105-
return -1;
106-
}
99+
// i64 find_lockset_by_memory_access_count(LockAccess *lock_set, u64 lock_set_len, u64 access_count) {
100+
// u64 i;
101+
// for (i = 0; i <= lock_set_len; i++) {
102+
// if (lock_set[i].memory_access_count == access_count) {
103+
// return i;
104+
// }
105+
// }
106+
// return -1;
107+
// }
107108

108109
u32 is_in_range(u64 num, u64 min, u64 max) {
109110
return (min <= num && num <= max);
@@ -118,18 +119,18 @@ void mem_analyse_exit() {
118119
printf("mem_write_set_len: %ld \n", threads[j].mem_write_set_len);
119120
printf("mem_read_set_len: %ld \n", threads[j].mem_read_set_len);
120121
printf("lock_state_set_len: %ld \n", threads[j].lock_state_set_len);
121-
122+
122123
u64 i;
124+
printf("locks: \n");
123125
for (i = 0; i <= threads[j].lock_state_set_len; i++) {
124-
printf("lock %ld, state: %d \n", threads[j].lock_state_set[i].addr, threads[j].lock_state_set[i].state);
126+
printf("lock addr: %ld, (end)state: %d \n", threads[j].lock_state_set[i].addr, threads[j].lock_state_set[i].state);
125127
}
126128

127129
free(threads[j].mem_write_set);
128130
free(threads[j].mem_read_set);
129131
free(threads[j].lock_state_set);
130-
free(threads[j].lock_write_held_set);
131-
free(threads[j].lock_read_held_set);
132132
}
133+
printf("debug error checks: %ld, ok checks: %ld \n", debug_error_checks, debug_ok_checks);
133134
}
134135

135136
u32 mem_analyse_init() {
@@ -163,20 +164,6 @@ u32 mem_analyse_new_thread_init(void *drcontext) {
163164
return 0;
164165
}
165166

166-
threads[n_threads].lock_write_held_set = (LockAccess*)malloc(sizeof(LockAccess) * linear_set_size_increment);
167-
threads[n_threads].lock_write_held_set_capacity = linear_set_size_increment;
168-
if (threads[n_threads].lock_write_held_set == NULL) {
169-
printf("set allocation error \n");
170-
return 0;
171-
}
172-
173-
threads[n_threads].lock_read_held_set = (LockAccess*)malloc(sizeof(LockAccess) * linear_set_size_increment);
174-
threads[n_threads].lock_read_held_set_capacity = linear_set_size_increment;
175-
if (threads[n_threads].lock_read_held_set == NULL) {
176-
printf("set allocation error \n");
177-
return 0;
178-
}
179-
180167
n_threads += 1;
181168
return 1;
182169
}
@@ -239,19 +226,10 @@ void wrap_pre_lock(void *wrapcxt, OUT void **user_data) {
239226
};
240227
}
241228
thread_accessed->lock_state_set[i].lock_count += 1;
242-
if (thread_accessed->lock_state_set[i].lock_count >= thread_accessed->lock_state_set[i].lock_count) {
243-
// now the lock is write held
244-
thread_accessed->lock_write_held_set[thread_accessed->lock_write_held_set_len].addr = (usize) addr;
245-
thread_accessed->lock_write_held_set[thread_accessed->lock_write_held_set_len].callee_thread_id = thread_id;
246-
thread_accessed->lock_write_held_set[thread_accessed->lock_write_held_set_len].memory_access_count = memory_access_counter;
247-
thread_accessed->lock_write_held_set_len += 1;
248-
} else {
249-
// read held
250-
thread_accessed->lock_read_held_set[thread_accessed->lock_read_held_set_len].addr = (usize) addr;
251-
thread_accessed->lock_read_held_set[thread_accessed->lock_read_held_set_len].callee_thread_id = thread_id;
252-
thread_accessed->lock_read_held_set[thread_accessed->lock_read_held_set_len].memory_access_count = memory_access_counter;
253-
thread_accessed->lock_read_held_set_len += 1;
229+
if (thread_accessed->lock_state_set[i].unlock_count <= thread_accessed->lock_state_set[i].lock_count) {
230+
thread_accessed->lock_state_set[i].state = WriteHeld;
254231
}
232+
thread_accessed->last_locked_mutex_addr = (usize)addr;
255233
}
256234

257235
void wrap_post_malloc(void *wrapcxt, void *user_data) {
@@ -268,7 +246,6 @@ void wrap_post_malloc(void *wrapcxt, void *user_data) {
268246
if (threads[j].thread_id == thread_id) break;
269247
if (j == n_threads-1) return;
270248
}
271-
// printf("ADDED %ld \n", thread_id);
272249
allocations[n_allocs].addr = (usize)addr;
273250
allocations[n_allocs].callee_thread_id = thread_id;
274251
allocations[n_allocs].size = size;
@@ -280,14 +257,25 @@ void wrap_pre_malloc(void *wrapcxt, OUT void **user_data) {
280257
}
281258

282259
void check_for_race(ThreadState *thread_state) {
283-
int write_set_i, read_set_i, lock_set_i1, lock_set_i2;
260+
int write_set_i, read_set_i;
284261
for (write_set_i = 0; write_set_i <= thread_state->mem_write_set_len; write_set_i++) {
285262
for (read_set_i = 0; read_set_i <= thread_state->mem_read_set_len; read_set_i++) {
263+
// check write-read pairs
286264
if (thread_state->mem_write_set[write_set_i].memory_access_count > thread_state->mem_read_set[read_set_i].memory_access_count) {
287-
if (find_lockset_by_memory_access_count(thread_state->lock_write_held_set, thread_state->lock_write_held_set_len, thread_state->lock_write_held_set[write_set_i].memory_access_count) == -1 || find_lockset_by_memory_access_count(thread_state->lock_write_held_set, thread_state->lock_write_held_set_len, thread_state->mem_read_set[read_set_i].memory_access_count) == -1) {
288-
printf("FOUND!\n");
265+
if(!thread_state->mem_write_set[write_set_i].has_lock && !thread_state->mem_read_set[write_set_i].has_lock) {
266+
// printf("found read race for addr: %ld in thread: %ld \n", thread_state->mem_write_set[write_set_i].address_accessed, thread_state->mem_write_set[write_set_i].callee_thread_id);
267+
debug_error_checks += 1;
268+
} else {
269+
debug_ok_checks += 1;
289270
}
290271
}
272+
// write write-read pairs
273+
if(!thread_state->mem_write_set[write_set_i].has_lock && !thread_state->mem_write_set[write_set_i+1].has_lock) {
274+
// printf("found write race for addr: %ld in thread: %ld \n", thread_state->mem_write_set[write_set_i].address_accessed, thread_state->mem_write_set[write_set_i].callee_thread_id);
275+
debug_error_checks += 1;
276+
} else {
277+
debug_ok_checks += 1;
278+
}
291279
}
292280
}
293281
}
@@ -311,7 +299,8 @@ void memtrace(void *drcontext, u64 thread_id) {
311299
for(j = 0; j < n_allocs; j++) {
312300
if (is_in_range((usize)mem_ref->addr, allocations[j].addr, allocations[j].addr + allocations[j].size)) break;
313301

314-
if (j >= n_allocs-1) {
302+
if (j >= n_allocs-1 || n_allocs == 0) {
303+
// printf("not found \n");
315304
// commiting a sin but goto is the most simple way to continue an outer loop in C
316305
goto continue_outer_loop;
317306
}
@@ -333,7 +322,14 @@ void memtrace(void *drcontext, u64 thread_id) {
333322
return;
334323
}
335324
ThreadState *thread_accessed = &threads[thread_states_index_owning_accessed_addr];
336-
// printf("adding %d \n", thread_states_index_owning_accessed_addr);
325+
326+
i32 lock_state_i;
327+
for (lock_state_i = 0; lock_state_i <= thread_accessed->lock_state_set_len; lock_state_i++) {
328+
if (thread_accessed->lock_state_set[lock_state_i].addr == thread_accessed->last_locked_mutex_addr) {
329+
break;
330+
}
331+
if (lock_state_i >= thread_accessed->lock_state_set_len) lock_state_i = -1;
332+
}
337333
if (mem_ref->type == 1 || mem_ref->type == 457 || mem_ref->type == 458 || mem_ref->type == 456 || mem_ref->type == 568) {
338334
// mem write
339335
if (thread_accessed->mem_write_set_len >= thread_accessed->mem_write_set_capacity) thread_accessed->mem_write_set = increase_set_capacity(thread_accessed->mem_write_set, &thread_accessed->mem_write_set_capacity);
@@ -343,6 +339,11 @@ void memtrace(void *drcontext, u64 thread_id) {
343339
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].callee_thread_id = thread_id;
344340
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].size = mem_ref->size;
345341
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].memory_access_count = memory_access_counter;
342+
if (lock_state_i != -1) {
343+
LockAccess la = {thread_accessed->lock_state_set[lock_state_i].state, thread_accessed->lock_state_set[lock_state_i].addr, thread_accessed->lock_state_set[lock_state_i].callee_thread_id};
344+
thread_accessed->mem_write_set[thread_accessed->mem_read_set_len].lock_access = la;
345+
thread_accessed->mem_write_set[thread_accessed->mem_read_set_len].has_lock = 1;
346+
}
346347
thread_accessed->mem_write_set_len += 1;
347348
} else if(mem_ref->type == 0 || mem_ref->type == 227 || mem_ref->type == 225 || mem_ref->type == 197 || mem_ref->type == 228 || mem_ref->type == 229 || mem_ref->type == 299 || mem_ref->type == 173) {
348349
// mem read
@@ -353,6 +354,11 @@ void memtrace(void *drcontext, u64 thread_id) {
353354
thread_accessed->mem_read_set[thread_accessed->mem_read_set_len].callee_thread_id = thread_id;
354355
thread_accessed->mem_read_set[thread_accessed->mem_read_set_len].size = mem_ref->size;
355356
thread_accessed->mem_read_set[thread_accessed->mem_read_set_len].memory_access_count = memory_access_counter;
357+
if (lock_state_i != -1) {
358+
LockAccess la = {thread_accessed->lock_state_set[lock_state_i].state, thread_accessed->lock_state_set[lock_state_i].addr, thread_accessed->lock_state_set[lock_state_i].callee_thread_id};
359+
thread_accessed->mem_read_set[thread_accessed->mem_read_set_len].lock_access = la;
360+
thread_accessed->mem_read_set[thread_accessed->mem_read_set_len].has_lock = 1;
361+
}
356362
thread_accessed->mem_read_set_len += 1;
357363
}
358364
check_for_race(thread_accessed);

testPrograms/basicMultiThread.c

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ int g = 0;
77
int *heap_storage;
88
pthread_mutex_t mutex_g = PTHREAD_MUTEX_INITIALIZER;
99

10+
void *detector_malloc(size_t size) {
11+
return malloc(size);
12+
}
13+
1014
void *myThreadFun(void *tid) {
1115
int myid = (int)tid;
1216

13-
pthread_mutex_lock(&mutex_g);
17+
// pthread_mutex_lock(&mutex_g);
1418
heap_storage[10] = 1;
15-
pthread_mutex_unlock(&mutex_g);
19+
// pthread_mutex_unlock(&mutex_g);
1620

1721
static int s = 0;
1822

@@ -26,7 +30,7 @@ int main() {
2630
int i;
2731
pthread_t tid;
2832

29-
heap_storage = malloc(4096*sizeof(int)); // same, without repeating the type name
33+
heap_storage = detector_malloc(4096*sizeof(int)); // same, without repeating the type name
3034

3135
// Let us create three threads
3236
for (i = 0; i < 3; i++)

0 commit comments

Comments
 (0)