浏览代码

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 年之前
父节点
当前提交
b7c1f1bf2f
共有 6 个文件被更改,包括 82 次插入9 次删除
  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 = []
 allocator = []
+debugger = []
 log = ["write", "alloc_id"]
 no_log_lock = ["log"]
 security = []
-testing = ["log"]
+testing = ["log", "debugger"]
 tls = []
 unsafe_no_brk_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
 
+Be sure to use Rust nightly.
+
 Add `ralloc` to `Cargo.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
 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
 
 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);
     }
 }
+
+/// 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 }
     }
+
+    /// 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> {

+ 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 \
                           block.");
 
-            res
+            // Mark the block uninitialized to the debugger.
+            res.mark_uninitialized()
         } else {
             // No fitting block found. Allocate a new block.
             self.alloc_external(size, align)
@@ -615,7 +616,8 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
         // Check consistency.
         self.check();
 
-        res
+        // Mark the block uninitialized to the debugger.
+        res.mark_uninitialized()
     }
 
     /// 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
             // 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.
             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
                       // 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.
@@ -838,7 +840,9 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
 
             // Truncate the vector.
             self.pool.truncate(new_len);
-            res
+
+            // Mark the block uninitialized to the debugger.
+            res.mark_uninitialized()
         } else {
             // Calculate the upper and lower bound
             let empty = self.pool[ind + 1].empty_left();
@@ -854,7 +858,8 @@ pub trait Allocator: ops::DerefMut<Target = Bookkeeper> {
                 *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(()) }
 }
 
+/// 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)]
 mod test {
     use super::*;