360 likes | 476 Views
Title Page. Debugging via Run-Time Type Checking. Alexey Loginov, Suan Yong, Susan Horwitz, Thomas Reps University of Wisconsin - Madison. Overview 1 - C. Run-Time Type Checking. C: weak static typing permits bugs unions (write one field, read another)
E N D
Title Page Debugging via Run-Time Type Checking Alexey Loginov, Suan Yong, Susan Horwitz, Thomas Reps University of Wisconsin - Madison
Overview 1 - C Run-Time Type Checking • C: weak static typing permits bugs • unions (write one field, read another) • casting (pointer with non-pointer value, pointer points to object with wrong type) • pointer arithmetic (array out-of-bounds)
Overview 2 - goal Run-Time Type Checking • Goal: help programmers find these bugs via run-time type checking • flag errors • track down their cause(fault localization)
Overview - Approach Approach: Track Types Dynamically • Tag each memory location with its dynamic type • in ‘mirror’ of memory • On use, verify that current dynamic type is appropriate • On assignment, verify that assigned type is consistent with static type
Tracking run-time types: Use Tracking Run-Time Types • Use of a location: dynamic type verified • wrong type: generate error message • Example uses: • int *p; • *p + 5; => error if p not a pointer => error if *p not an integer
Tracking run-time types: Assignment Tracking Run-Time Types • Assignment: dynamic type propagated. • different type: generate warning message • helpful in tracking down root cause • Example assignment: • int x, *q; • x = *q; => tag of x set to tag of *q => warn if *q not an integer
Purify, Safe C ... Where we fit in • Purify [Hastings+] • array out-of-bounds, bad pointer dereferences, use of uninitialized data • memory leaks • Safe C [Austin+] • stale pointer uses, unions • Our Approach • run-time type violations • some ability to track down logical errors(not just flag symptoms)
Talk Overview • Introduction • Examples • Implementation • Results from Initial Tests • Future Work • Conclusion
Ex1: Unions 1 (memory) (tags) : : FE232F10 unalloc : : : : 00000000 uninit 00000000 uninit : : : : Example 1: Unions union U { int u1; int *u2; } u; int *p; u.u1 = 8; p = u.u2; *p = 0; (0x08) (u) 00000008 int (p)
Ex1: Unions 2 (memory) (tags) : : FE232F10 unalloc : : : : 00000008 uninit 00000000 uninit : : : : warning Example 1: Unions union U { int u1; int *u2; } u; int *p; u.u1 = 8; p = u.u2; *p = 0; (0x08) (u) int (p) 00000008 int
Ex1: Unions 3 (memory) (tags) : : FE232F10 unalloc : : : : error! 00000008 uninit 00000008 uninit : : : : Example 1: Unions union U { int u1; int *u2; } u; int *p; u.u1 = 8; p = u.u2; *p = 0; (0x08) (u) int (p) int
Ex1: Unions 4 (memory) (tags) : : FE232F10 unalloc : : : : error! 00000008 uninit 00000008 uninit : : : : Example 1: Unions union U { int u1; int *u2; } u; int *p; u.u1 = 8; p = u.u2; *p = 0; (0x08) (u) int (p) int
Ex 2: Array (heap) 1 Example 2: Bad Pointer Access int *intArray = (int *) malloc(15 * sizeof(int)); int **ptrArray = (int **) malloc(15 * sizeof(int *)); memory intArray ptrArray
Ex 2: Array (heap) 2 i intArray ptrArray padding PURIFY Example 2: Bad Pointer Access int *i, sumEven = 0; for(i = intArray; ...; i += 2)sumEven += *i; i memory intArray ptrArray DTC
Ex 3: Array (stack) 1 i i Example 3: Bad Pointer Access (stack) int intArray[10]; int *ptrArray[10]; for(i = intArray; ...; i += 2) ... stack intArray ptrArray DTC stack intArray ptrArray PURIFY
Ex 4: Sim Inherit 1 Example 4: Simulated Inheritance struct Sup { int a1; int a2; } sup; struct Sub { int b1; int b2; char b3; } sub; sup sub a1 a2 b1 b2 b3 int int int int char
Ex 4: Sim Inherit 2 s s f(&sup); f(&sub); Example 4: Simulated Inheritance void f(struct Sup *s) { printInt(s->a1); printInt(s->a2); } sup sub a1 a2 b1 b2 b3 int int int int char
Ex 4: Sim Inherit 3 Example 4: Simulated Inheritance struct Sub { int b1; float f1; int b2; char b3; } sub; struct Sup { int a1; int a2; } sup; a1 a2 b1 f1 b2 b3 int int int float int char
Ex 4: Sim Inherit 4 s s error! f(&sup); f(&sub); Example 4: Simulated Inheritance void f(struct Sup *s) { printInt(s->a1); printInt(s->a2); } a1 a2 b1 f1 b2 b3 int int int float int char
Talk Overview • Introduction • Examples • Implementation • Results from Initial Tests • Future Work • Conclusion
Overview of Tool Overview of Tool • Instrumentation • preprocessed source file instrumented C file • Compilation • Execution • tracks types in “mirror” of memory • writes error/warning messages, sends signal • signal can be intercepted by GDB for further debugging
Impl: Mirror and Tags 0 0 1 0 1 1 1 0 0 0 0 0 0 0 1 0 Mirror and Tags USER MEM MIRROR continuation bits (currently unused bits) 0 type bits: {unalloc, uninit, integral, real, pointer} size bits (log2)
Impl: Tags (examples) 0 0 0 0 0 integral uninit integral integral real 1 1 0 1 0 1 integral size=2 0 0 0 0 0 0 unalloc size=4 Mirror and Tags char 0 short 0 float 0 char [3](third element uninitialized) 0
Source Level X-lation: C to C Source Level Translation • C source C source • Using Lucent’s Ckit front end • syntax-directed transformations • instruments statements and expressions to set, verify, and propagate tags • preserves expression values, types, side-effects • makes extensive use of “comma” operator • introduces many temporary variables • separate (partial) instrumentation
Instr Example: x = *p (1) *(tmp2 = &x) * (verifyTag(&p, ptr_type), p) *(tmp3 =(verifyTag(&p, ptr_type), p), verifyPtr(tmp3, sizeof(int)), tmp3) int x; int *p; x = *p
Instr Example: x = *p (2) *(tmp2 = &x) = *(tmp3 =(verifyTag(&p, ptr_type), p), verifyPtr(tmp3, sizeof(int)), tmp3) *(tmp2 = &x) x = *p *(tmp3 =(verifyTag(&p, ptr_type), p), verifyPtr(tmp3, sizeof(int)), tmp3)
Instr Example: x = *p (3) x = *p *(tmp2 = &x) = *(tmp3 =(verifyTag(&p, ptr_type), p), verifyPtr(tmp3, sizeof(int)), tmp3) (tmp1 = copyTag(tmp2, tmp3, int_type), tmp1)
Talk Overview • Introduction • Examples • Implementation • Results from Initial Tests • Future Work • Conclusion
Effectiveness of Tool Effectiveness of Tool • Bugs identified in: • Solaris utilities (nroff, plot, ul, ...) • Olden benchmarks (health, voronoi) • Output usually succinct • Error messages pinpoint bug symptoms • Warning messages help track down logical error
Sample bug descriptions Sample Bugs and Errors Reported • stray pointer corrupts return address on stack • error: pointer dereference incorrectly typed • stray pointer corrupts _iob array (stdin, stdout, stderr) • error: referencing unallocated memory • treats malloc’ed memory as zero-initialized • error: use of uninitialized memory
Alterations in Behavior Alterations in Behavior • Hard to preserve behavior of non-portable programs • Purify affects go, nroff, others • modified memory layout • Our tool affects ul, units, col • local variable addition • register variable demotion • Behavior sometimes altered but cause of error is the same
Performance – by slowdown (table) Performance run-time (sec) Mean = 43.8 Median = 23.9 Solaris utils Olden SPECint95
Areas for Improvement: overhead, spurious errors Areas for Improvement • Overhead • current slowdown: 6x - 130x • can improve instrumentation and macro definitions • no attempt yet made to identify and remove unnecessary checks • Spurious errors and warnings • abundant in a few benchmarks • due to memset, calloc, etc. • incomplete extern types (__ctype[])
Optimization: no intervening write Planned Optimizations • Remove redundant checks • when there are no intervening writes (of a different type) to a given location x += 10; int x, y, z; if (x < 0){ y = (x > 5) ? x : 0; }
Optimization: static analysis Planned Optimizations • Remove redundant instrumentation • use static analysis • to find variables that need no instrumentation • i.e. variables that can be statically determined to be type-safe • has potential to drastically reduce overhead
Conclusion Conclusion • Run-time type checking works • identified pointer and array access errors • warnings help find root cause of error • Potential for finding subtler type-bugs • no “real” examples found in preliminary testing • Slowdown is relatively high • potential for significant speedup untapped