1 1.1 christos /* 2 1.1 christos * testcode/lock_verify.c - verifier program for lock traces, checks order. 3 1.1 christos * 4 1.1 christos * Copyright (c) 2007, NLnet Labs. All rights reserved. 5 1.1 christos * 6 1.1 christos * This software is open source. 7 1.1 christos * 8 1.1 christos * Redistribution and use in source and binary forms, with or without 9 1.1 christos * modification, are permitted provided that the following conditions 10 1.1 christos * are met: 11 1.1 christos * 12 1.1 christos * Redistributions of source code must retain the above copyright notice, 13 1.1 christos * this list of conditions and the following disclaimer. 14 1.1 christos * 15 1.1 christos * Redistributions in binary form must reproduce the above copyright notice, 16 1.1 christos * this list of conditions and the following disclaimer in the documentation 17 1.1 christos * and/or other materials provided with the distribution. 18 1.1 christos * 19 1.1 christos * Neither the name of the NLNET LABS nor the names of its contributors may 20 1.1 christos * be used to endorse or promote products derived from this software without 21 1.1 christos * specific prior written permission. 22 1.1 christos * 23 1.1 christos * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 1.1 christos * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 1.1 christos * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 1.1 christos * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 1.1 christos * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 1.1 christos * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 1.1 christos * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 1.1 christos * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 1.1 christos * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 1.1 christos * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 1.1 christos * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 1.1 christos */ 35 1.1 christos 36 1.1 christos /** 37 1.1 christos * \file 38 1.1 christos * 39 1.1 christos * This file checks the lock traces generated by checklock.c. 40 1.1 christos * Checks if locks are consistently locked in the same order. 41 1.1 christos * If not, this can lead to deadlock if threads execute the different 42 1.1 christos * ordering at the same time. 43 1.1 christos * 44 1.1 christos */ 45 1.1 christos 46 1.1 christos #include "config.h" 47 1.1 christos #ifdef HAVE_TIME_H 48 1.1 christos #include <time.h> 49 1.1 christos #endif 50 1.1 christos #include "util/log.h" 51 1.1 christos #include "util/rbtree.h" 52 1.1 christos #include "util/locks.h" 53 1.1 christos #include "util/fptr_wlist.h" 54 1.1 christos 55 1.1 christos /* --- data structures --- */ 56 1.1 christos struct lock_ref; 57 1.1 christos 58 1.1 christos /** keep track of lock id in lock-verify application 59 1.1 christos * Also defined in smallapp/worker_cb.c for fptr_wlist encapsulation 60 1.1 christos * breakage (the security tests break encapsulation for this test app) */ 61 1.1 christos struct order_id { 62 1.1 christos /** the thread id that created it */ 63 1.1 christos int thr; 64 1.1 christos /** the instance number of creation */ 65 1.1 christos int instance; 66 1.1 christos }; 67 1.1 christos 68 1.1 christos /** a lock */ 69 1.1 christos struct order_lock { 70 1.1 christos /** rbnode in all tree */ 71 1.1.1.2 christos rbnode_type node; 72 1.1 christos /** lock id */ 73 1.1 christos struct order_id id; 74 1.1 christos /** the creation file */ 75 1.1 christos char* create_file; 76 1.1 christos /** creation line */ 77 1.1 christos int create_line; 78 1.1 christos /** set of all locks that are smaller than this one (locked earlier) */ 79 1.1.1.2 christos rbtree_type* smaller; 80 1.1 christos /** during depthfirstsearch, this is a linked list of the stack 81 1.1 christos * of locks. points to the next lock bigger than this one. */ 82 1.1 christos struct lock_ref* dfs_next; 83 1.1 christos /** if lock has been visited (all smaller locks have been compared to 84 1.1 christos * this lock), only need to compare this with all unvisited(bigger) 85 1.1 christos * locks */ 86 1.1 christos int visited; 87 1.1 christos }; 88 1.1 christos 89 1.1 christos /** reference to a lock in a rbtree set */ 90 1.1 christos struct lock_ref { 91 1.1 christos /** rbnode, key is an order_id ptr */ 92 1.1.1.2 christos rbnode_type node; 93 1.1 christos /** the lock referenced */ 94 1.1 christos struct order_lock* lock; 95 1.1 christos /** why is this ref */ 96 1.1 christos char* file; 97 1.1 christos /** line number */ 98 1.1 christos int line; 99 1.1 christos }; 100 1.1 christos 101 1.1 christos /** count of errors detected */ 102 1.1 christos static int errors_detected = 0; 103 1.1 christos /** verbose? */ 104 1.1 christos static int verb = 0; 105 1.1 christos 106 1.1 christos /** print program usage help */ 107 1.1 christos static void 108 1.1.1.2 christos usage(void) 109 1.1 christos { 110 1.1 christos printf("lock_verify <trace files>\n"); 111 1.1 christos } 112 1.1 christos 113 1.1 christos /** read header entry. 114 1.1 christos * @param in: file to read header of. 115 1.1 christos * @return: False if it does not belong to the rest. */ 116 1.1 christos static int 117 1.1 christos read_header(FILE* in) 118 1.1 christos { 119 1.1 christos time_t t; 120 1.1 christos pid_t p; 121 1.1 christos int thrno; 122 1.1 christos static int have_values = 0; 123 1.1 christos static time_t the_time; 124 1.1 christos static pid_t the_pid; 125 1.1 christos static int threads[256]; 126 1.1 christos 127 1.1 christos if(fread(&t, sizeof(t), 1, in) != 1 || 128 1.1 christos fread(&thrno, sizeof(thrno), 1, in) != 1 || 129 1.1 christos fread(&p, sizeof(p), 1, in) != 1) { 130 1.1 christos fatal_exit("fread failed"); 131 1.1 christos } 132 1.1 christos /* check these values are sorta OK */ 133 1.1 christos if(!have_values) { 134 1.1 christos the_time = t; 135 1.1 christos the_pid = p; 136 1.1 christos memset(threads, 0, 256*sizeof(int)); 137 1.1 christos if(thrno >= 256) { 138 1.1 christos fatal_exit("Thread number too big. %d", thrno); 139 1.1 christos } 140 1.1 christos threads[thrno] = 1; 141 1.1 christos have_values = 1; 142 1.1 christos printf(" trace %d from pid %u on %s", thrno, 143 1.1 christos (unsigned)p, ctime(&t)); 144 1.1 christos } else { 145 1.1 christos if(the_pid != p) { 146 1.1 christos printf(" has pid %u, not %u. Skipped.\n", 147 1.1 christos (unsigned)p, (unsigned)the_pid); 148 1.1 christos return 0; 149 1.1 christos } 150 1.1 christos if(threads[thrno]) 151 1.1 christos fatal_exit("same threadno in two files"); 152 1.1 christos threads[thrno] = 1; 153 1.1 christos if( abs((int)(the_time - t)) > 3600) 154 1.1 christos fatal_exit("input files from different times: %u %u", 155 1.1 christos (unsigned)the_time, (unsigned)t); 156 1.1 christos printf(" trace of thread %u:%d\n", (unsigned)p, thrno); 157 1.1 christos } 158 1.1 christos return 1; 159 1.1 christos } 160 1.1 christos 161 1.1 christos /** max length of strings: filenames and function names. */ 162 1.1 christos #define STRMAX 1024 163 1.1 christos /** read a string from file, false on error */ 164 1.1 christos static int readup_str(char** str, FILE* in) 165 1.1 christos { 166 1.1 christos char buf[STRMAX]; 167 1.1 christos int len = 0; 168 1.1 christos int c; 169 1.1 christos /* ends in zero */ 170 1.1 christos while( (c = fgetc(in)) != 0) { 171 1.1 christos if(c == EOF) 172 1.1 christos fatal_exit("eof in readstr, file too short"); 173 1.1 christos buf[len++] = c; 174 1.1 christos if(len == STRMAX) { 175 1.1 christos fatal_exit("string too long, bad file format"); 176 1.1 christos } 177 1.1 christos } 178 1.1 christos buf[len] = 0; 179 1.1 christos *str = strdup(buf); 180 1.1.1.4 christos if(!*str) 181 1.1.1.4 christos fatal_exit("strdup failed: out of memory"); 182 1.1 christos return 1; 183 1.1 christos } 184 1.1 christos 185 1.1 christos /** read creation entry */ 186 1.1.1.2 christos static void read_create(rbtree_type* all, FILE* in) 187 1.1 christos { 188 1.1 christos struct order_lock* o = calloc(1, sizeof(struct order_lock)); 189 1.1 christos if(!o) fatal_exit("malloc failure"); 190 1.1 christos if(fread(&o->id.thr, sizeof(int), 1, in) != 1 || 191 1.1 christos fread(&o->id.instance, sizeof(int), 1, in) != 1 || 192 1.1 christos !readup_str(&o->create_file, in) || 193 1.1 christos fread(&o->create_line, sizeof(int), 1, in) != 1) 194 1.1 christos fatal_exit("fread failed"); 195 1.1 christos o->smaller = rbtree_create(order_lock_cmp); 196 1.1 christos o->node.key = &o->id; 197 1.1 christos if(!rbtree_insert(all, &o->node)) { 198 1.1 christos /* already inserted */ 199 1.1 christos struct order_lock* a = (struct order_lock*)rbtree_search(all, 200 1.1 christos &o->id); 201 1.1 christos log_assert(a); 202 1.1 christos a->create_file = o->create_file; 203 1.1 christos a->create_line = o->create_line; 204 1.1 christos free(o->smaller); 205 1.1 christos free(o); 206 1.1 christos o = a; 207 1.1 christos } 208 1.1 christos if(verb) printf("read create %u %u %s %d\n", 209 1.1 christos (unsigned)o->id.thr, (unsigned)o->id.instance, 210 1.1 christos o->create_file, o->create_line); 211 1.1 christos } 212 1.1 christos 213 1.1 christos /** insert lock entry (empty) into list */ 214 1.1 christos static struct order_lock* 215 1.1.1.2 christos insert_lock(rbtree_type* all, struct order_id* id) 216 1.1 christos { 217 1.1 christos struct order_lock* o = calloc(1, sizeof(struct order_lock)); 218 1.1 christos if(!o) fatal_exit("malloc failure"); 219 1.1 christos o->smaller = rbtree_create(order_lock_cmp); 220 1.1 christos o->id = *id; 221 1.1 christos o->node.key = &o->id; 222 1.1 christos if(!rbtree_insert(all, &o->node)) 223 1.1 christos fatal_exit("insert fail should not happen"); 224 1.1 christos return o; 225 1.1 christos } 226 1.1 christos 227 1.1 christos /** read lock entry */ 228 1.1.1.2 christos static void read_lock(rbtree_type* all, FILE* in, int val) 229 1.1 christos { 230 1.1 christos struct order_id prev_id, now_id; 231 1.1 christos struct lock_ref* ref; 232 1.1 christos struct order_lock* prev, *now; 233 1.1 christos ref = (struct lock_ref*)calloc(1, sizeof(struct lock_ref)); 234 1.1 christos if(!ref) fatal_exit("malloc failure"); 235 1.1 christos prev_id.thr = val; 236 1.1 christos if(fread(&prev_id.instance, sizeof(int), 1, in) != 1 || 237 1.1 christos fread(&now_id.thr, sizeof(int), 1, in) != 1 || 238 1.1 christos fread(&now_id.instance, sizeof(int), 1, in) != 1 || 239 1.1 christos !readup_str(&ref->file, in) || 240 1.1 christos fread(&ref->line, sizeof(int), 1, in) != 1) 241 1.1 christos fatal_exit("fread failed"); 242 1.1 christos if(verb) printf("read lock %u %u %u %u %s %d\n", 243 1.1 christos (unsigned)prev_id.thr, (unsigned)prev_id.instance, 244 1.1 christos (unsigned)now_id.thr, (unsigned)now_id.instance, 245 1.1 christos ref->file, ref->line); 246 1.1 christos /* find the two locks involved */ 247 1.1 christos prev = (struct order_lock*)rbtree_search(all, &prev_id); 248 1.1 christos now = (struct order_lock*)rbtree_search(all, &now_id); 249 1.1 christos /* if not there - insert 'em */ 250 1.1 christos if(!prev) prev = insert_lock(all, &prev_id); 251 1.1 christos if(!now) now = insert_lock(all, &now_id); 252 1.1 christos ref->lock = prev; 253 1.1 christos ref->node.key = &prev->id; 254 1.1 christos if(!rbtree_insert(now->smaller, &ref->node)) { 255 1.1 christos free(ref->file); 256 1.1 christos free(ref); 257 1.1 christos } 258 1.1 christos } 259 1.1 christos 260 1.1 christos /** read input file */ 261 1.1.1.2 christos static void readinput(rbtree_type* all, char* file) 262 1.1 christos { 263 1.1 christos FILE *in = fopen(file, "r"); 264 1.1 christos int fst; 265 1.1 christos if(!in) { 266 1.1 christos perror(file); 267 1.1 christos exit(1); 268 1.1 christos } 269 1.1 christos printf("file %s", file); 270 1.1 christos if(!read_header(in)) { 271 1.1 christos fclose(in); 272 1.1 christos return; 273 1.1 christos } 274 1.1 christos while(fread(&fst, sizeof(fst), 1, in) == 1) { 275 1.1 christos if(fst == -1) 276 1.1 christos read_create(all, in); 277 1.1 christos else read_lock(all, in, fst); 278 1.1 christos } 279 1.1 christos fclose(in); 280 1.1 christos } 281 1.1 christos 282 1.1 christos /** print cycle message */ 283 1.1 christos static void found_cycle(struct lock_ref* visit, int level) 284 1.1 christos { 285 1.1 christos struct lock_ref* p; 286 1.1 christos int i = 0; 287 1.1 christos errors_detected++; 288 1.1 christos printf("Found inconsistent locking order of length %d\n", level); 289 1.1 christos printf("for lock %d %d created %s %d\n", 290 1.1 christos visit->lock->id.thr, visit->lock->id.instance, 291 1.1 christos visit->lock->create_file, visit->lock->create_line); 292 1.1 christos printf("sequence is:\n"); 293 1.1 christos p = visit; 294 1.1 christos while(p) { 295 1.1 christos struct order_lock* next = 296 1.1 christos p->lock->dfs_next?p->lock->dfs_next->lock:visit->lock; 297 1.1 christos printf("[%d] is locked at line %s %d before lock %d %d\n", 298 1.1 christos i, p->file, p->line, next->id.thr, next->id.instance); 299 1.1 christos printf("[%d] lock %d %d is created at %s %d\n", 300 1.1 christos i, next->id.thr, next->id.instance, 301 1.1 christos next->create_file, next->create_line); 302 1.1 christos i++; 303 1.1 christos p = p->lock->dfs_next; 304 1.1 christos if(p && p->lock == visit->lock) 305 1.1 christos break; 306 1.1 christos } 307 1.1 christos } 308 1.1 christos 309 1.1 christos /** Detect cycle by comparing visited now with all (unvisited) bigger nodes */ 310 1.1 christos static int detect_cycle(struct lock_ref* visit, struct lock_ref* from) 311 1.1 christos { 312 1.1 christos struct lock_ref* p = from; 313 1.1 christos while(p) { 314 1.1 christos if(p->lock == visit->lock) 315 1.1 christos return 1; 316 1.1 christos p = p->lock->dfs_next; 317 1.1 christos } 318 1.1 christos return 0; 319 1.1 christos } 320 1.1 christos 321 1.1 christos /** recursive function to depth first search for cycles. 322 1.1 christos * @param visit: the lock visited at this step. 323 1.1 christos * its dfs_next pointer gives the visited lock up in recursion. 324 1.1 christos * same as lookfor at level 0. 325 1.1 christos * @param level: depth of recursion. 0 is start. 326 1.1 christos * @param from: search for matches from unvisited node upwards. 327 1.1 christos */ 328 1.1 christos static void search_cycle(struct lock_ref* visit, int level, 329 1.1 christos struct lock_ref* from) 330 1.1 christos { 331 1.1 christos struct lock_ref* ref; 332 1.1 christos /* check for cycle */ 333 1.1 christos if(detect_cycle(visit, from) && level != 0) { 334 1.1 christos found_cycle(visit, level); 335 1.1 christos fatal_exit("found lock order cycle"); 336 1.1 christos } 337 1.1 christos /* recurse */ 338 1.1 christos if(!visit->lock->visited) 339 1.1 christos from = visit; 340 1.1 christos if(verb > 1) fprintf(stderr, "[%d] visit lock %u %u %s %d\n", level, 341 1.1 christos (unsigned)visit->lock->id.thr, 342 1.1 christos (unsigned)visit->lock->id.instance, 343 1.1 christos visit->lock->create_file, visit->lock->create_line); 344 1.1 christos RBTREE_FOR(ref, struct lock_ref*, visit->lock->smaller) { 345 1.1 christos ref->lock->dfs_next = visit; 346 1.1 christos search_cycle(ref, level+1, from); 347 1.1 christos } 348 1.1 christos visit->lock->visited = 1; 349 1.1 christos } 350 1.1 christos 351 1.1 christos /** Check ordering of one lock */ 352 1.1 christos static void check_order_lock(struct order_lock* lock) 353 1.1 christos { 354 1.1 christos struct lock_ref start; 355 1.1 christos if(lock->visited) return; 356 1.1 christos 357 1.1 christos start.node.key = &lock->id; 358 1.1 christos start.lock = lock; 359 1.1 christos start.file = lock->create_file; 360 1.1 christos start.line = lock->create_line; 361 1.1 christos 362 1.1 christos if(!lock->create_file) 363 1.1 christos log_err("lock %u %u does not have create info", 364 1.1 christos (unsigned)lock->id.thr, (unsigned)lock->id.instance); 365 1.1 christos 366 1.1 christos /* depth first search to find cycle with this lock at head */ 367 1.1 christos lock->dfs_next = NULL; 368 1.1 christos search_cycle(&start, 0, &start); 369 1.1 christos } 370 1.1 christos 371 1.1 christos /** Check ordering of locks */ 372 1.1.1.2 christos static void check_order(rbtree_type* all_locks) 373 1.1 christos { 374 1.1 christos /* check each lock */ 375 1.1 christos struct order_lock* lock; 376 1.1 christos int i=0; 377 1.1 christos RBTREE_FOR(lock, struct order_lock*, all_locks) { 378 1.1 christos if(verb) 379 1.1 christos printf("[%d/%d] Checking lock %d %d %s %d\n", 380 1.1 christos i, (int)all_locks->count, 381 1.1 christos lock->id.thr, lock->id.instance, 382 1.1 christos lock->create_file, lock->create_line); 383 1.1 christos else if (i % ((all_locks->count/75)<1?1:all_locks->count/75) 384 1.1 christos == 0) 385 1.1 christos fprintf(stderr, "."); 386 1.1 christos i++; 387 1.1 christos check_order_lock(lock); 388 1.1 christos } 389 1.1 christos fprintf(stderr, "\n"); 390 1.1 christos } 391 1.1 christos 392 1.1.1.3 christos /** delete lock ref */ 393 1.1.1.3 christos static void dellockref(rbnode_type* node, void* ATTR_UNUSED(arg)) 394 1.1.1.3 christos { 395 1.1.1.3 christos struct lock_ref* o = (struct lock_ref*)node; 396 1.1.1.3 christos if(!o) return; 397 1.1.1.3 christos free(o->file); 398 1.1.1.3 christos free(o); 399 1.1.1.3 christos } 400 1.1.1.3 christos 401 1.1.1.3 christos /** delete lock node */ 402 1.1.1.3 christos static void delnode(rbnode_type* node, void* ATTR_UNUSED(arg)) 403 1.1.1.3 christos { 404 1.1.1.3 christos struct order_lock* o = (struct order_lock*)node; 405 1.1.1.3 christos if(!o) return; 406 1.1.1.3 christos free(o->create_file); 407 1.1.1.3 christos if(o->smaller) { 408 1.1.1.3 christos traverse_postorder(o->smaller, &dellockref, NULL); 409 1.1.1.3 christos free(o->smaller); 410 1.1.1.3 christos } 411 1.1.1.3 christos free(o); 412 1.1.1.3 christos } 413 1.1.1.3 christos 414 1.1.1.3 christos /** delete allocated memory */ 415 1.1.1.3 christos static void locks_free(rbtree_type* all_locks) 416 1.1.1.3 christos { 417 1.1.1.3 christos if(!all_locks) 418 1.1.1.3 christos return; 419 1.1.1.3 christos traverse_postorder(all_locks, &delnode, NULL); 420 1.1.1.3 christos free(all_locks); 421 1.1.1.3 christos } 422 1.1.1.3 christos 423 1.1 christos /** main program to verify all traces passed */ 424 1.1 christos int 425 1.1 christos main(int argc, char* argv[]) 426 1.1 christos { 427 1.1.1.2 christos rbtree_type* all_locks; 428 1.1 christos int i; 429 1.1 christos time_t starttime = time(NULL); 430 1.1 christos #ifdef USE_THREAD_DEBUG 431 1.1 christos /* do not overwrite the ublocktrace files with the ones generated 432 1.1 christos * by this program (i.e. when the log code creates a lock) */ 433 1.1 christos check_locking_order = 0; 434 1.1 christos #endif 435 1.1 christos if(argc <= 1) { 436 1.1 christos usage(); 437 1.1 christos return 1; 438 1.1 christos } 439 1.1.1.3 christos checklock_start(); 440 1.1 christos log_init(NULL, 0, NULL); 441 1.1 christos log_ident_set("lock-verify"); 442 1.1 christos /* init */ 443 1.1 christos all_locks = rbtree_create(order_lock_cmp); 444 1.1 christos errors_detected = 0; 445 1.1 christos 446 1.1 christos /* read the input files */ 447 1.1 christos for(i=1; i<argc; i++) { 448 1.1 christos readinput(all_locks, argv[i]); 449 1.1 christos } 450 1.1 christos 451 1.1 christos /* check ordering */ 452 1.1 christos check_order(all_locks); 453 1.1 christos 454 1.1 christos /* do not free a thing, OS will do it */ 455 1.1 christos printf("checked %d locks in %d seconds with %d errors.\n", 456 1.1 christos (int)all_locks->count, (int)(time(NULL)-starttime), 457 1.1 christos errors_detected); 458 1.1.1.3 christos locks_free(all_locks); 459 1.1 christos if(errors_detected) return 1; 460 1.1 christos return 0; 461 1.1 christos } 462