Skip to content

Commit 7c33fa9

Browse files
committed
fixing indexing issue in race detection algorithm
1 parent 555154c commit 7c33fa9

File tree

3 files changed

+31
-51
lines changed

3 files changed

+31
-51
lines changed

README.md

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
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 `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.
4-
This project is a simplified but accurate representation of the, in section 4.3, described "precise, slow" detection mode.
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. The race detector is based on [this](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/35604.pdf) paper.
4+
This project is a simplified but accurate representation of the in section 4.3, described "precise, slow" detection mode.
55
All events are "exported" in instrument.h and implemented by race_detector.c (fed to DynamoRIO by `instrument.c`).
6-
76
The detector is fully build on a clock-vector before/after relations mechanism to detect races.
87

8+
## Practicability
9+
10+
The project is able to detect basic write-write and write-read data races. Since DynamoRIO isn't super stable in multithreaded environments, the results can vary, so it's good practice to check the sets on potential differences(although there were no changes done to the checked program) if reproducibility is required.
11+
912
## Clock Vector Based Race Detection
1013

11-
Clock Vector Based detection can get very complex very quickly, thus this implementation doesn't quantities memory accesses but instead check after every access, thus the clock vector doesn't need to contain accurate timestamps(instead a memory access counter is used). This isn't very performant and the slowest, but also most simple method.
14+
Clock Vector Based detection can get very complex, very quickly, thus this implementation doesn't quantities memory accesses but instead check after every access, thus the clock vector doesn't need to contain accurate timestamps(instead a memory access counter is used). This isn't very performant and the slowest, but also most simple method.
1215

1316
The mathematical operators and logic is described in [this](https://dl.acm.org/doi/pdf/10.1145/3018610.3018611)(section 3.12) paper quite well.
1417

1518
## Rough implementation summary
1619

17-
Most of the race detectors code comes down to collecting and preperation of data. The detector has per thread data and a global allocations array(which stores context information about allocated memory that has to be checked). The per thread data contains sets that store all reads/ writes relating to allocated memory as well as all lock accesses and states. Checks are performed on every memory access to allocated memory.
20+
Most of the race detector's code comes down to collecting and preparation of data. The detector has per thread data and a global allocations/locks array(which stores context information about allocated memory that has to be checked, and lock states). The per thread data contains sets that store all reads/ writes relating to allocated memory, as well as all lock accesses and states. Checks are performed on every memory access to allocated memory.

race_detector.c

+18-40
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ u32 mem_analyse_new_thread_init(void *drcontext) {
150150
printf("set allocation error \n");
151151
return 0;
152152
}
153-
printf("new thread: %ld \n", thread_id);
153+
// printf("new thread: %ld \n", thread_id);
154154
n_program_threads += 1;
155155
return 1;
156156
}
@@ -161,13 +161,10 @@ void mem_analyse_thread_exit() {
161161

162162

163163
void wrap_pre_unlock(void *wrapcxt, OUT void **user_data) {
164-
// printf("pre UNLOCK\n");
165164
void *addr = drwrap_get_arg(wrapcxt, 0);
166165
u64 thread_id = dr_get_thread_id(dr_get_current_drcontext());
167-
// printf("pthread_unlock called\n");
168166
i64 t_index = find_thread_by_tid(thread_id);
169167
if (t_index < 0) {
170-
// printf("error finding thread_id. %ld \n", thread_id);
171168
return;
172169
}
173170
ThreadState *thread_accessed = &program_threads[t_index];
@@ -189,19 +186,16 @@ void wrap_pre_unlock(void *wrapcxt, OUT void **user_data) {
189186

190187
pthread_mutex_lock(&mutex_program_locks);
191188
program_locks[i].unlock_count += 1;
192-
if (program_locks[i].unlock_count >= program_locks[i].lock_count) {
189+
if (program_locks[i].lock_count > program_locks[i].unlock_count) {
190+
program_locks[i].state = WriteHeld;
191+
} else if (program_locks[i].lock_count <= program_locks[i].unlock_count) {
193192
program_locks[i].state = ReadHeld;
194193
}
195194
// thread_accessed->last_locked_mutex_addr = -1;
196195
pthread_mutex_unlock(&mutex_program_locks);
197196
}
198197
// todo => handle post lock/unlock and check wether it was successfull!.
199198
void wrap_pre_lock(void *wrapcxt, OUT void **user_data) {
200-
printf("pre LOCK \n");
201-
// wait(10000000000000);
202-
int i;
203-
// for(i = 0; i <= 100000000; i++) {}
204-
205199
// printf("pre LOCK\n");
206200
void *addr = drwrap_get_arg(wrapcxt, 0);
207201
// printf("locking: %ld \n", addr);
@@ -217,7 +211,7 @@ void wrap_pre_lock(void *wrapcxt, OUT void **user_data) {
217211
pthread_mutex_lock(&mutex_program_threads);
218212
thread_accessed->last_locked_mutex_addr = (usize)addr;
219213
pthread_mutex_unlock(&mutex_program_threads);
220-
// int i;
214+
int i;
221215
for (i = 0; i <= n_program_locks; i++) {
222216
if (program_locks[i].addr == (usize)addr) break;
223217
if (i >= n_program_locks) {
@@ -235,9 +229,10 @@ void wrap_pre_lock(void *wrapcxt, OUT void **user_data) {
235229

236230
pthread_mutex_lock(&mutex_program_locks);
237231
program_locks[i].lock_count += 1;
238-
// todo => check wether it has to be < or <=
239-
if (program_locks[i].unlock_count <= program_locks[i].lock_count) {
232+
if (program_locks[i].lock_count > program_locks[i].unlock_count) {
240233
program_locks[i].state = WriteHeld;
234+
} else if (program_locks[i].lock_count <= program_locks[i].unlock_count) {
235+
program_locks[i].state = ReadHeld;
241236
}
242237
pthread_mutex_unlock(&mutex_program_locks);
243238
}
@@ -269,14 +264,10 @@ void wrap_pre_malloc(void *wrapcxt, OUT void **user_data) {
269264
}
270265

271266
void check_for_race(ThreadState *thread_state) {
272-
int i;
273-
for (i = 0; i <= thread_state->mem_write_set_len; i++) {
274-
printf("state %d \n", thread_state->mem_write_set[i].lock_access.state);
275-
}
276267
int thread_i, write_set_i, write_set_i_plus1, read_set_i;
277268
for (write_set_i = 0; write_set_i < thread_state->mem_write_set_len; write_set_i++) {
278269
for (thread_i = 0; thread_i < n_program_threads; thread_i++) {
279-
if (program_threads[thread_i].thread_id == thread_state->thread_id) continue;
270+
if (program_threads[thread_i].thread_id != thread_state->thread_id) continue;
280271
ThreadState *iterated_thread = &program_threads[thread_i];
281272
// check write-read pairs
282273
for (read_set_i = 0; read_set_i < iterated_thread->mem_read_set_len; read_set_i++) {
@@ -292,10 +283,8 @@ void check_for_race(ThreadState *thread_state) {
292283
// write write-read pairs
293284
for (write_set_i_plus1 = 0; write_set_i_plus1 < iterated_thread->mem_write_set_len; write_set_i_plus1++) {
294285
if (thread_state->mem_write_set[write_set_i].address_accessed == iterated_thread->mem_write_set[write_set_i_plus1].address_accessed) {
295-
printf("%ld == %ld \n", thread_state->mem_write_set[write_set_i].lock_access.state, iterated_thread->mem_write_set[write_set_i_plus1].lock_access.state);
296286
if(thread_state->mem_write_set[write_set_i].lock_access.state != WriteHeld && iterated_thread->mem_write_set[write_set_i_plus1].lock_access.state != WriteHeld) {
297287
detected_races_counter += 1;
298-
printf("race on: %ld \n", thread_state->mem_write_set[write_set_i].address_accessed);
299288
break;
300289
}
301290
}
@@ -308,15 +297,15 @@ void check_for_race(ThreadState *thread_state) {
308297

309298
// this is an event like fn that is envoked on every memory access (called by DynamRIO)
310299
void memtrace(void *drcontext, u64 thread_id) {
311-
// printf("trace: %ld \n",thread_id );
312300
if (drcontext == NULL) return;
313301
per_thread_t *data;
314302
mem_ref_t *mem_ref, *buf_ptr;
315303
data = drmgr_get_tls_field(drcontext, tls_idx);
316304
buf_ptr = BUF_PTR(data->seg_base);
317305

318-
i64 t_index = find_thread_by_tid(thread_id);
319-
if (t_index < 0) return;
306+
// todo => fix: mutex information is loaded from wrong thread. it's loaded from the last_locked_mutex_addr from the thread_accessed(the thread to which or from which the information is written/read) instead in which thread it took place.
307+
i64 curr_thread_index = find_thread_by_tid(thread_id);
308+
if (curr_thread_index < 0) return;
320309
for (mem_ref = (mem_ref_t *)data->buf_base; mem_ref < buf_ptr; mem_ref++) {
321310
int j;
322311

@@ -335,10 +324,8 @@ void memtrace(void *drcontext, u64 thread_id) {
335324
}
336325
}
337326
memory_access_counter++;
338-
printf("-- mem access from %ld \n", thread_id);
339327
if (memory_access_counter >= LLONG_MAX) DR_ASSERT(false);
340328
u64 thread_id_owning_accessed_addr = program_allocations[j].callee_thread_id;
341-
// printf("COMING THROUGH %ld \n", thread_id_owning_accessed_addr);
342329

343330
i64 thread_states_index_owning_accessed_addr = find_thread_by_tid(thread_id_owning_accessed_addr);
344331
if (thread_states_index_owning_accessed_addr < 0) {
@@ -347,38 +334,32 @@ void memtrace(void *drcontext, u64 thread_id) {
347334
return;
348335
}
349336
ThreadState *thread_accessed = &program_threads[thread_states_index_owning_accessed_addr];
337+
ThreadState *curr_thread = &program_threads[curr_thread_index];
350338
pthread_mutex_lock(&mutex_program_threads);
351339
i32 lock_state_i;
352-
// printf("%ld %ld\n", thread_accessed->last_locked_mutex_addr, thread_accessed->thread_id);
353-
if (thread_accessed->last_locked_mutex_addr != -1) {
340+
if (curr_thread->last_locked_mutex_addr != -1) {
354341
for (lock_state_i = 0; lock_state_i <= n_program_locks; lock_state_i++) {
355-
if (program_locks[lock_state_i].addr == thread_accessed->last_locked_mutex_addr) {
356-
// printf("found: %ld %ld \n", program_locks[lock_state_i].addr, thread_accessed->last_locked_mutex_addr);
342+
if (program_locks[lock_state_i].addr == curr_thread->last_locked_mutex_addr) {
357343
break;
358344
}
359345
if (lock_state_i >= n_program_locks) lock_state_i = -1;
360346
}
361347
} else {
362-
lock_state_i = thread_accessed->last_locked_mutex_addr;
348+
lock_state_i = curr_thread->last_locked_mutex_addr;
363349
}
364350
if (mem_ref->type == 1 || mem_ref->type == 457 || mem_ref->type == 458 || mem_ref->type == 456 || mem_ref->type == 568) {
365351
// mem write
366352
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);
367353
if (thread_accessed->mem_write_set == NULL) exit(1);
368354
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].address_accessed = (usize)mem_ref->addr;
369-
// printf("added to write set: %ld \n", (usize)mem_ref->addr);
370355
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].opcode = mem_ref->type;
371356
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].callee_thread_id = thread_id;
372357
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].size = mem_ref->size;
373358
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].memory_access_count = memory_access_counter;
374359
if (lock_state_i != -1) {
375360
LockAccess la = {program_locks[lock_state_i].state, program_locks[lock_state_i].addr, program_locks[lock_state_i].callee_thread_id};
376-
thread_accessed->mem_write_set[thread_accessed->mem_read_set_len].lock_access = la;
377-
thread_accessed->mem_write_set[thread_accessed->mem_read_set_len].has_lock = 1;
378-
printf("-- held %d \n", program_locks[lock_state_i].state);
379-
} else {
380-
// todo => should not be invoked
381-
printf("-- no lock for addr %d \n", mem_ref->addr);
361+
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].lock_access = la;
362+
thread_accessed->mem_write_set[thread_accessed->mem_write_set_len].has_lock = 1;
382363
}
383364
thread_accessed->mem_write_set_len += 1;
384365
} 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) {
@@ -394,9 +375,6 @@ void memtrace(void *drcontext, u64 thread_id) {
394375
LockAccess la = {program_locks[lock_state_i].state, program_locks[lock_state_i].addr, program_locks[lock_state_i].callee_thread_id};
395376
thread_accessed->mem_read_set[thread_accessed->mem_read_set_len].lock_access = la;
396377
thread_accessed->mem_read_set[thread_accessed->mem_read_set_len].has_lock = 1;
397-
printf("-- held %d \n", program_locks[lock_state_i].state);
398-
} else {
399-
printf("-- no lock for addr %d \n", mem_ref->addr);
400378
}
401379
thread_accessed->mem_read_set_len += 1;
402380
}

testPrograms/basicMultiThread.c

+5-6
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ void *detector_malloc(size_t size) {
1414
void *myThreadFun(void *tid) {
1515
int myid = (int)tid;
1616

17-
// pthread_mutex_lock(&mutex_g);
17+
pthread_mutex_lock(&mutex_g);
18+
printf("heap_storage index 10: %d \n", heap_storage[10]);
1819
heap_storage[10] = 1;
19-
// pthread_mutex_unlock(&mutex_g);
20-
// printf("write doen \n");
21-
// heap_storage[11] = 1;
22-
// printf("heap_storage index 10: %d \n", heap_storage[10]);
20+
pthread_mutex_unlock(&mutex_g);
2321
}
2422

2523
int main() {
@@ -34,7 +32,8 @@ int main() {
3432
last_tid = pthread_create(&tid, NULL, myThreadFun, (void *)tid);
3533
// pthread_join(last_tid, NULL);
3634

35+
// insures that the program doesn't quit before the threads didn't end
36+
// pthread_joind confuses Dynamorio and wait syscalls get blocked so this is an ugly solution
3737
for (i = 0; i <= 10000000; i++) {}
38-
// pthread_join(last_tid, NULL);
3938
return 0;
4039
}

0 commit comments

Comments
 (0)