1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
/*! **A global allocator for Wasm that traces allocations and deallocations for debugging purposes.** [![](https://docs.rs/wasm-tracing-allocator/badge.svg)](https://docs.rs/wasm-tracing-allocator/) [![](https://img.shields.io/crates/v/wasm-tracing-allocator.svg)](https://crates.io/crates/wasm-tracing-allocator) [![](https://img.shields.io/crates/d/wasm-tracing-allocator.svg)](https://crates.io/crates/wasm-tracing-allocator) [![Build Status](https://dev.azure.com/rustwasm/wasm-tracing-allocator/_apis/build/status/rustwasm.wasm-tracing-allocator?branchName=master)](https://dev.azure.com/rustwasm/wasm-tracing-allocator/_build/latest?definitionId=2&branchName=master) `wasm-tracing-allocator` enables you to better debug and analyze memory leaks and invalid frees in an environment where we don't have access to the conventional tools like Valgrind. The tracing hooks are safely implemented in JS, outside the Wasm module and its linear memory, to ensure that the tracing code doesn't perturb results. ## Table of Contents * [Enabling the Tracing Allocator](#enabling-the-tracing-allocator) * [Analyzing and Debugging](#analyzing-and-debugging) ## Enabling the Tracing Allocator First, add `wasm-tracing-allocator` to your `Cargo.toml`'s dependency list: ```toml [dependencies] wasm-tracing-allocator = "0.1.0" ``` Next, configure `wasm_tracing_allocator::WasmTracingAllocator` as the global allocator: ```no_run // src/lib.rs # fn main() {} use std::alloc::System; use wasm_tracing_allocator::WasmTracingAllocator; #[global_allocator] static GLOBAL_ALLOCATOR: WasmTracingAllocator<System> = WasmTracingAllocator(System); ``` Finally, make the JS implementations of the tracing hooks are available for your Wasm module to import: * On the Web, add this script *before* your Wasm module is instantiated: ```html <script src="https://unpkg.com/wasm-tracing-allocator@0.1.0/js/hooks.js"></script> ``` * On Node.js, require the hooks *before* your Wasm module is instantiated: ```js require("wasm-tracing-allocator"); ``` ## Analyzing and Debugging Use your developer tools console to invoke methods of the global `WasmTracingAllocator` object to get analyses about allocations and deallocations. The output is typically rendered with `console.table`: [![Example output](https://raw.githubusercontent.com/rustwasm/wasm-tracing-allocator/master/live-allocations-dump.png)](https://raw.githubusercontent.com/rustwasm/wasm-tracing-allocator/master/live-allocations-dump.png) ### `WasmTracingAllocator.dumpLiveAllocations` Dump a table of live allocations to the console. ```js WasmTracingAllocator.dumpLiveAllocations({ keyLabel: String, valueLabel: String, getKey: Object => any, getValue: Object => Number, }); ``` * `keyLabel`: Optional. The string label used to describe the keys column in the table. * `valueLabel`: Optional. The string label used to describe the values column in the table. * `getKey`: Optional. Function from an allocation entry object to anything. The table will group and aggregate entries by their keys. Defaults to the stack at the time of the allocation. * `getValue`: Optional. Function from an allocation entry object to a number. The values for all entries with the same key are summed. Defaults to the byte size of each allocation; a potential alternative would be to ignore the argument and return `1` to count the number of allocations instead. ### `WasmTracingAllocator.dumpInvalidFrees` Dump a table of invalid frees (double frees, frees of things that were never allocated, etc...) to the console. ```js WasmTracingAllocator.dumpInvalidFrees({ keyLabel: String, valueLabel: String, getKey: Object => any, getValue: Object => Number, }); ``` * `keyLabel`: Optional. The string label used to describe the keys column in the table. * `valueLabel`: Optional. The string label used to describe the values column in the table. * `getKey`: Optional. Function from an invalid free entry object to anything. The table will group and aggregate entries by their keys. Defaults to the stack at the time of the deallocation. * `getValue`: Optional. Function from an invalid free entry object to a number. The values for all entries with the same key are summed. Defaults to counting the number of invalid frees. */ #![deny(missing_docs, missing_debug_implementations)] use std::alloc::{GlobalAlloc, Layout}; #[doc(hidden)] pub mod hooks; /// A global allocator that traces the Wasm module's allocations and /// deallocations. /// /// It wraps some global allocator `A` that actually implements the allocation /// and deallocation, and inserts its tracing after each invocation. /// /// ## Example /// /// Just give it the global allocator `A` to wrap, and add the /// `#[global_allocator]` attribute. The module level documentation has an /// example of wrapping the default system allocator. Here is an example of /// wrapping [`wee_alloc`](https://github.com/rustwasm/wee_alloc): /// /// ```ignore /// // src/lib.rs /// # fn main() {} /// /// use wasm_tracing_allocator::WasmTracingAllocator; /// use wee_alloc::WeeAlloc; /// /// #[global_allocator] /// static GLOBAL_ALLOCATOR: WasmTracingAllocator<WeeAlloc> = /// WasmTracingAllocator(WeeAlloc::INIT); /// ``` #[derive(Debug)] pub struct WasmTracingAllocator<A>(pub A) where A: GlobalAlloc; unsafe impl<A> GlobalAlloc for WasmTracingAllocator<A> where A: GlobalAlloc, { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { let size = layout.size(); let align = layout.align(); let pointer = self.0.alloc(layout); hooks::on_alloc(size, align, pointer); pointer } unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) { let size = layout.size(); let align = layout.align(); self.0.dealloc(pointer, layout); hooks::on_dealloc(size, align, pointer); } unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { let size = layout.size(); let align = layout.align(); let pointer = self.0.alloc_zeroed(layout); hooks::on_alloc_zeroed(size, align, pointer); pointer } unsafe fn realloc(&self, old_pointer: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { let old_size = layout.size(); let align = layout.align(); let new_pointer = self.0.realloc(old_pointer, layout, new_size); hooks::on_realloc(old_pointer, new_pointer, old_size, new_size, align); new_pointer } }