Browse Source

Valgrind support.

We add first-class debugger support by providing functions for marking memory blocks uninitialized/free to the debugger. The default implementation is wired to valgrind, but it can easily be replaced.
ticki 8 years ago
parent
commit
b7c1f1bf2f
6 changed files with 82 additions and 9 deletions
  1. 2 1
      Cargo.toml
  2. 9 0
      README.md
  3. 21 0
      shim/src/lib.rs
  4. 21 0
      src/block.rs
  5. 13 8
      src/bookkeeper.rs
  6. 16 0
      src/sys.rs

+ 2 - 1
Cargo.toml

@@ -37,10 +37,11 @@ default = ["allocator", "clippy", "tls"]
 # ---
 # ---
 alloc_id = []
 alloc_id = []
 allocator = []
 allocator = []
+debugger = []
 log = ["write", "alloc_id"]
 log = ["write", "alloc_id"]
 no_log_lock = ["log"]
 no_log_lock = ["log"]
 security = []
 security = []
-testing = ["log"]
+testing = ["log", "debugger"]
 tls = []
 tls = []
 unsafe_no_brk_lock = []
 unsafe_no_brk_lock = []
 unsafe_no_mutex_lock = []
 unsafe_no_mutex_lock = []

+ 9 - 0
README.md

@@ -21,6 +21,8 @@ I consider the state of the code quality very good.
 
 
 ## Using ralloc
 ## Using ralloc
 
 
+Be sure to use Rust nightly.
+
 Add `ralloc` to `Cargo.toml`:
 Add `ralloc` to `Cargo.toml`:
 
 
 ```toml
 ```toml
@@ -46,6 +48,13 @@ Ralloc makes use of a global-local model allowing one to allocate or deallocate
 without locks, syncronization, or atomic writes. This provides reasonable
 without locks, syncronization, or atomic writes. This provides reasonable
 performance, while preserving flexibility and ability to multithread.
 performance, while preserving flexibility and ability to multithread.
 
 
+### First-class debugger (default: valgrind) support
+
+`ralloc` gives data to two debugger symbols specified in `ralloc_shim`, when
+the `debugger` feature is enabled. The default `shim` implementation is wired
+to `valgrind`, which can thus be used with `ralloc` to detect memory leaks and
+uninitialized use out-of-the-box.
+
 ### Custom out-of-memory handlers
 ### Custom out-of-memory handlers
 
 
 You can set custom OOM handlers, by:
 You can set custom OOM handlers, by:

+ 21 - 0
shim/src/lib.rs

@@ -90,3 +90,24 @@ pub mod thread_destructor {
         _tlv_atexit(dtor, t);
         _tlv_atexit(dtor, t);
     }
     }
 }
 }
+
+/// Debugging.
+pub mod debug {
+    use libc;
+
+    extern {
+        /// Valgrind symbol to declare memory undefined.
+        fn valgrind_make_mem_undefined(ptr: *const libc::c_void, size: libc::size_t);
+        /// Valgrind symbol to declare memory freed.
+        fn valgrind_freelike_block(ptr: *const libc::c_void, size: libc::size_t);
+    }
+
+    /// Mark this segment undefined to the debugger.
+    pub fn mark_undefined(ptr: *const libc::c_void, size: libc::size_t) {
+        unsafe { valgrind_make_mem_undefined(ptr, size) }
+    }
+    /// Mark this segment free to the debugger.
+    pub fn mark_free(ptr: *const libc::c_void, size: libc::size_t) {
+        unsafe { valgrind_freelike_block(ptr, size) }
+    }
+}

+ 21 - 0
src/block.rs

@@ -229,6 +229,27 @@ impl Block {
             ))
             ))
         } else { None }
         } else { None }
     }
     }
+
+    /// Mark this block free to the debugger.
+    ///
+    /// The debugger might do things like memleak and use-after-free checks. This methods informs
+    /// the debugger that this block is freed.
+    #[inline]
+    pub fn mark_free(self) -> Block {
+        sys::mark_free(*self.ptr as *const u8, self.size);
+
+        self
+    }
+
+    /// Mark this block uninitialized to the debugger.
+    ///
+    /// To detect use-after-free, the allocator need to mark
+    #[inline]
+    pub fn mark_uninitialized(self) -> Block {
+        sys::mark_uninitialized(*self.ptr as *const u8, self.size);
+
+        self
+    }
 }
 }
 
 
 impl From<Block> for Pointer<u8> {
 impl From<Block> for Pointer<u8> {

+ 13 - 8
src/bookkeeper.rs

@@ -327,7 +327,8 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
             debug_assert!(res.size() == size, "Requested space does not match with the returned \
             debug_assert!(res.size() == size, "Requested space does not match with the returned \
                           block.");
                           block.");
 
 
-            res
+            // Mark the block uninitialized to the debugger.
+            res.mark_uninitialized()
         } else {
         } else {
             // No fitting block found. Allocate a new block.
             // No fitting block found. Allocate a new block.
             self.alloc_external(size, align)
             self.alloc_external(size, align)
@@ -615,7 +616,8 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
         // Check consistency.
         // Check consistency.
         self.check();
         self.check();
 
 
-        res
+        // Mark the block uninitialized to the debugger.
+        res.mark_uninitialized()
     }
     }
 
 
     /// Push an element without reserving.
     /// Push an element without reserving.
@@ -645,8 +647,8 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
             // Merging failed. Note that trailing empty blocks are not allowed, hence the last block is
             // Merging failed. Note that trailing empty blocks are not allowed, hence the last block is
             // the only non-empty candidate which may be adjacent to `block`.
             // the only non-empty candidate which may be adjacent to `block`.
 
 
-            // We push.
-            let res = self.pool.push(block);
+            // Mark it free and push.
+            let res = self.pool.push(block.mark_free());
 
 
             // Make some assertions.
             // Make some assertions.
             debug_assert!(res.is_ok(), "Push failed (buffer full).");
             debug_assert!(res.is_ok(), "Push failed (buffer full).");
@@ -813,8 +815,8 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
                       // `if` block above with the conditional that `gap` is `None`, which is the
                       // `if` block above with the conditional that `gap` is `None`, which is the
                       // case where the closure is evaluated.
                       // case where the closure is evaluated.
 
 
-            // Set the element.
-            ptr::write(self.pool.get_unchecked_mut(ind), block);
+            // Mark it free and set the element.
+            ptr::write(self.pool.get_unchecked_mut(ind), block.mark_free());
         }
         }
 
 
         // Free the old buffer, if it exists.
         // Free the old buffer, if it exists.
@@ -838,7 +840,9 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
 
 
             // Truncate the vector.
             // Truncate the vector.
             self.pool.truncate(new_len);
             self.pool.truncate(new_len);
-            res
+
+            // Mark the block uninitialized to the debugger.
+            res.mark_uninitialized()
         } else {
         } else {
             // Calculate the upper and lower bound
             // Calculate the upper and lower bound
             let empty = self.pool[ind + 1].empty_left();
             let empty = self.pool[ind + 1].empty_left();
@@ -854,7 +858,8 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
                 *place = empty2.empty_left();
                 *place = empty2.empty_left();
             }
             }
 
 
-            res
+            // Mark the block uninitialized to the debugger.
+            res.mark_uninitialized()
         }
         }
     }
     }
 }
 }

+ 16 - 0
src/sys.rs

@@ -70,6 +70,22 @@ pub fn log(s: &str) -> Result<(), ()> {
     if shim::log(s) == -1 { Err(()) } else { Ok(()) }
     if shim::log(s) == -1 { Err(()) } else { Ok(()) }
 }
 }
 
 
+/// Tell the debugger that this segment is free.
+///
+/// If the `debugger` feature is disabled, this is a NOOP.
+pub fn mark_free(_ptr: *const u8, _size: usize) {
+    #[cfg(feature = "debugger")]
+    shim::debug::mark_free(_ptr as *const _, _size);
+}
+
+/// Tell the debugger that this segment is unaccessible.
+///
+/// If the `debugger` feature is disabled, this is a NOOP.
+pub fn mark_uninitialized(_ptr: *const u8, _size: usize) {
+    #[cfg(feature = "debugger")]
+    shim::debug::mark_free(_ptr as *const _, _size);
+}
+
 #[cfg(test)]
 #[cfg(test)]
 mod test {
 mod test {
     use super::*;
     use super::*;