aboutsummaryrefslogtreecommitdiff
path: root/testcode/lock_verify.c
diff options
context:
space:
mode:
Diffstat (limited to 'testcode/lock_verify.c')
-rw-r--r--testcode/lock_verify.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/testcode/lock_verify.c b/testcode/lock_verify.c
new file mode 100644
index 000000000000..9f51c3385a6b
--- /dev/null
+++ b/testcode/lock_verify.c
@@ -0,0 +1,418 @@
+/*
+ * testcode/lock_verify.c - verifier program for lock traces, checks order.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file checks the lock traces generated by checklock.c.
+ * Checks if locks are consistently locked in the same order.
+ * If not, this can lead to deadlock if threads execute the different
+ * ordering at the same time.
+ *
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "util/rbtree.h"
+#include "util/locks.h"
+#include "util/fptr_wlist.h"
+
+/* --- data structures --- */
+struct lock_ref;
+
+/** keep track of lock id in lock-verify application
+ * Also defined in smallapp/worker_cb.c for fptr_wlist encapsulation
+ * breakage (the security tests break encapsulation for this test app) */
+struct order_id {
+ /** the thread id that created it */
+ int thr;
+ /** the instance number of creation */
+ int instance;
+};
+
+/** a lock */
+struct order_lock {
+ /** rbnode in all tree */
+ rbnode_t node;
+ /** lock id */
+ struct order_id id;
+ /** the creation file */
+ char* create_file;
+ /** creation line */
+ int create_line;
+ /** set of all locks that are smaller than this one (locked earlier) */
+ rbtree_t* smaller;
+ /** during depthfirstsearch, this is a linked list of the stack
+ * of locks. points to the next lock bigger than this one. */
+ struct lock_ref* dfs_next;
+ /** if lock has been visited (all smaller locks have been compared to
+ * this lock), only need to compare this with all unvisited(bigger)
+ * locks */
+ int visited;
+};
+
+/** reference to a lock in a rbtree set */
+struct lock_ref {
+ /** rbnode, key is an order_id ptr */
+ rbnode_t node;
+ /** the lock referenced */
+ struct order_lock* lock;
+ /** why is this ref */
+ char* file;
+ /** line number */
+ int line;
+};
+
+/** count of errors detected */
+static int errors_detected = 0;
+/** verbose? */
+static int verb = 0;
+
+/** print program usage help */
+static void
+usage()
+{
+ printf("lock_verify <trace files>\n");
+}
+
+/** read header entry.
+ * @param in: file to read header of.
+ * @return: False if it does not belong to the rest. */
+static int
+read_header(FILE* in)
+{
+ time_t t;
+ pid_t p;
+ int thrno;
+ static int have_values = 0;
+ static time_t the_time;
+ static pid_t the_pid;
+ static int threads[256];
+
+ if(fread(&t, sizeof(t), 1, in) != 1 ||
+ fread(&thrno, sizeof(thrno), 1, in) != 1 ||
+ fread(&p, sizeof(p), 1, in) != 1) {
+ fatal_exit("fread failed");
+ }
+ /* check these values are sorta OK */
+ if(!have_values) {
+ the_time = t;
+ the_pid = p;
+ memset(threads, 0, 256*sizeof(int));
+ if(thrno >= 256) {
+ fatal_exit("Thread number too big. %d", thrno);
+ }
+ threads[thrno] = 1;
+ have_values = 1;
+ printf(" trace %d from pid %u on %s", thrno,
+ (unsigned)p, ctime(&t));
+ } else {
+ if(the_pid != p) {
+ printf(" has pid %u, not %u. Skipped.\n",
+ (unsigned)p, (unsigned)the_pid);
+ return 0;
+ }
+ if(threads[thrno])
+ fatal_exit("same threadno in two files");
+ threads[thrno] = 1;
+ if( abs((int)(the_time - t)) > 3600)
+ fatal_exit("input files from different times: %u %u",
+ (unsigned)the_time, (unsigned)t);
+ printf(" trace of thread %u:%d\n", (unsigned)p, thrno);
+ }
+ return 1;
+}
+
+/** max length of strings: filenames and function names. */
+#define STRMAX 1024
+/** read a string from file, false on error */
+static int readup_str(char** str, FILE* in)
+{
+ char buf[STRMAX];
+ int len = 0;
+ int c;
+ /* ends in zero */
+ while( (c = fgetc(in)) != 0) {
+ if(c == EOF)
+ fatal_exit("eof in readstr, file too short");
+ buf[len++] = c;
+ if(len == STRMAX) {
+ fatal_exit("string too long, bad file format");
+ }
+ }
+ buf[len] = 0;
+ *str = strdup(buf);
+ return 1;
+}
+
+/** read creation entry */
+static void read_create(rbtree_t* all, FILE* in)
+{
+ struct order_lock* o = calloc(1, sizeof(struct order_lock));
+ if(!o) fatal_exit("malloc failure");
+ if(fread(&o->id.thr, sizeof(int), 1, in) != 1 ||
+ fread(&o->id.instance, sizeof(int), 1, in) != 1 ||
+ !readup_str(&o->create_file, in) ||
+ fread(&o->create_line, sizeof(int), 1, in) != 1)
+ fatal_exit("fread failed");
+ o->smaller = rbtree_create(order_lock_cmp);
+ o->node.key = &o->id;
+ if(!rbtree_insert(all, &o->node)) {
+ /* already inserted */
+ struct order_lock* a = (struct order_lock*)rbtree_search(all,
+ &o->id);
+ log_assert(a);
+ a->create_file = o->create_file;
+ a->create_line = o->create_line;
+ free(o->smaller);
+ free(o);
+ o = a;
+ }
+ if(verb) printf("read create %u %u %s %d\n",
+ (unsigned)o->id.thr, (unsigned)o->id.instance,
+ o->create_file, o->create_line);
+}
+
+/** insert lock entry (empty) into list */
+static struct order_lock*
+insert_lock(rbtree_t* all, struct order_id* id)
+{
+ struct order_lock* o = calloc(1, sizeof(struct order_lock));
+ if(!o) fatal_exit("malloc failure");
+ o->smaller = rbtree_create(order_lock_cmp);
+ o->id = *id;
+ o->node.key = &o->id;
+ if(!rbtree_insert(all, &o->node))
+ fatal_exit("insert fail should not happen");
+ return o;
+}
+
+/** read lock entry */
+static void read_lock(rbtree_t* all, FILE* in, int val)
+{
+ struct order_id prev_id, now_id;
+ struct lock_ref* ref;
+ struct order_lock* prev, *now;
+ ref = (struct lock_ref*)calloc(1, sizeof(struct lock_ref));
+ if(!ref) fatal_exit("malloc failure");
+ prev_id.thr = val;
+ if(fread(&prev_id.instance, sizeof(int), 1, in) != 1 ||
+ fread(&now_id.thr, sizeof(int), 1, in) != 1 ||
+ fread(&now_id.instance, sizeof(int), 1, in) != 1 ||
+ !readup_str(&ref->file, in) ||
+ fread(&ref->line, sizeof(int), 1, in) != 1)
+ fatal_exit("fread failed");
+ if(verb) printf("read lock %u %u %u %u %s %d\n",
+ (unsigned)prev_id.thr, (unsigned)prev_id.instance,
+ (unsigned)now_id.thr, (unsigned)now_id.instance,
+ ref->file, ref->line);
+ /* find the two locks involved */
+ prev = (struct order_lock*)rbtree_search(all, &prev_id);
+ now = (struct order_lock*)rbtree_search(all, &now_id);
+ /* if not there - insert 'em */
+ if(!prev) prev = insert_lock(all, &prev_id);
+ if(!now) now = insert_lock(all, &now_id);
+ ref->lock = prev;
+ ref->node.key = &prev->id;
+ if(!rbtree_insert(now->smaller, &ref->node)) {
+ free(ref->file);
+ free(ref);
+ }
+}
+
+/** read input file */
+static void readinput(rbtree_t* all, char* file)
+{
+ FILE *in = fopen(file, "r");
+ int fst;
+ if(!in) {
+ perror(file);
+ exit(1);
+ }
+ printf("file %s", file);
+ if(!read_header(in)) {
+ fclose(in);
+ return;
+ }
+ while(fread(&fst, sizeof(fst), 1, in) == 1) {
+ if(fst == -1)
+ read_create(all, in);
+ else read_lock(all, in, fst);
+ }
+ fclose(in);
+}
+
+/** print cycle message */
+static void found_cycle(struct lock_ref* visit, int level)
+{
+ struct lock_ref* p;
+ int i = 0;
+ errors_detected++;
+ printf("Found inconsistent locking order of length %d\n", level);
+ printf("for lock %d %d created %s %d\n",
+ visit->lock->id.thr, visit->lock->id.instance,
+ visit->lock->create_file, visit->lock->create_line);
+ printf("sequence is:\n");
+ p = visit;
+ while(p) {
+ struct order_lock* next =
+ p->lock->dfs_next?p->lock->dfs_next->lock:visit->lock;
+ printf("[%d] is locked at line %s %d before lock %d %d\n",
+ i, p->file, p->line, next->id.thr, next->id.instance);
+ printf("[%d] lock %d %d is created at %s %d\n",
+ i, next->id.thr, next->id.instance,
+ next->create_file, next->create_line);
+ i++;
+ p = p->lock->dfs_next;
+ if(p && p->lock == visit->lock)
+ break;
+ }
+}
+
+/** Detect cycle by comparing visited now with all (unvisited) bigger nodes */
+static int detect_cycle(struct lock_ref* visit, struct lock_ref* from)
+{
+ struct lock_ref* p = from;
+ while(p) {
+ if(p->lock == visit->lock)
+ return 1;
+ p = p->lock->dfs_next;
+ }
+ return 0;
+}
+
+/** recursive function to depth first search for cycles.
+ * @param visit: the lock visited at this step.
+ * its dfs_next pointer gives the visited lock up in recursion.
+ * same as lookfor at level 0.
+ * @param level: depth of recursion. 0 is start.
+ * @param from: search for matches from unvisited node upwards.
+ */
+static void search_cycle(struct lock_ref* visit, int level,
+ struct lock_ref* from)
+{
+ struct lock_ref* ref;
+ /* check for cycle */
+ if(detect_cycle(visit, from) && level != 0) {
+ found_cycle(visit, level);
+ fatal_exit("found lock order cycle");
+ }
+ /* recurse */
+ if(!visit->lock->visited)
+ from = visit;
+ if(verb > 1) fprintf(stderr, "[%d] visit lock %u %u %s %d\n", level,
+ (unsigned)visit->lock->id.thr,
+ (unsigned)visit->lock->id.instance,
+ visit->lock->create_file, visit->lock->create_line);
+ RBTREE_FOR(ref, struct lock_ref*, visit->lock->smaller) {
+ ref->lock->dfs_next = visit;
+ search_cycle(ref, level+1, from);
+ }
+ visit->lock->visited = 1;
+}
+
+/** Check ordering of one lock */
+static void check_order_lock(struct order_lock* lock)
+{
+ struct lock_ref start;
+ if(lock->visited) return;
+
+ start.node.key = &lock->id;
+ start.lock = lock;
+ start.file = lock->create_file;
+ start.line = lock->create_line;
+
+ if(!lock->create_file)
+ log_err("lock %u %u does not have create info",
+ (unsigned)lock->id.thr, (unsigned)lock->id.instance);
+
+ /* depth first search to find cycle with this lock at head */
+ lock->dfs_next = NULL;
+ search_cycle(&start, 0, &start);
+}
+
+/** Check ordering of locks */
+static void check_order(rbtree_t* all_locks)
+{
+ /* check each lock */
+ struct order_lock* lock;
+ int i=0;
+ RBTREE_FOR(lock, struct order_lock*, all_locks) {
+ if(verb)
+ printf("[%d/%d] Checking lock %d %d %s %d\n",
+ i, (int)all_locks->count,
+ lock->id.thr, lock->id.instance,
+ lock->create_file, lock->create_line);
+ else if (i % ((all_locks->count/75)<1?1:all_locks->count/75)
+ == 0)
+ fprintf(stderr, ".");
+ i++;
+ check_order_lock(lock);
+ }
+ fprintf(stderr, "\n");
+}
+
+/** main program to verify all traces passed */
+int
+main(int argc, char* argv[])
+{
+ rbtree_t* all_locks;
+ int i;
+ time_t starttime = time(NULL);
+ if(argc <= 1) {
+ usage();
+ return 1;
+ }
+ log_init(NULL, 0, NULL);
+ log_ident_set("lock-verify");
+ /* init */
+ all_locks = rbtree_create(order_lock_cmp);
+ errors_detected = 0;
+
+ /* read the input files */
+ for(i=1; i<argc; i++) {
+ readinput(all_locks, argv[i]);
+ }
+
+ /* check ordering */
+ check_order(all_locks);
+
+ /* do not free a thing, OS will do it */
+ printf("checked %d locks in %d seconds with %d errors.\n",
+ (int)all_locks->count, (int)(time(NULL)-starttime),
+ errors_detected);
+ if(errors_detected) return 1;
+ return 0;
+}