memgrind.go (7556B)
1 // Copyright 2021 The Libc Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !libc.membrk && libc.memgrind 6 // +build !libc.membrk,libc.memgrind 7 8 // This is a debug-only version of the memory handling functions. When a 9 // program is built with -tags=libc.memgrind the functions MemAuditStart and 10 // MemAuditReport can be used to check for memory leaks. 11 12 package libc // import "modernc.org/libc" 13 14 import ( 15 "fmt" 16 "runtime" 17 "sort" 18 "strings" 19 "unsafe" 20 21 "modernc.org/libc/errno" 22 "modernc.org/libc/sys/types" 23 "modernc.org/memory" 24 ) 25 26 const memgrind = true 27 28 type memReportItem struct { 29 p, pc uintptr 30 s string 31 } 32 33 func (it *memReportItem) String() string { 34 more := it.s 35 if more != "" { 36 a := strings.Split(more, "\n") 37 more = "\n\t\t" + strings.Join(a, "\n\t\t") 38 } 39 return fmt.Sprintf("\t%s: %#x%s", pc2origin(it.pc), it.p, more) 40 } 41 42 type memReport []memReportItem 43 44 func (r memReport) Error() string { 45 a := []string{"memory leaks"} 46 for _, v := range r { 47 a = append(a, v.String()) 48 } 49 return strings.Join(a, "\n") 50 } 51 52 var ( 53 allocator memory.Allocator 54 allocs map[uintptr]uintptr // addr: caller 55 allocsMore map[uintptr]string 56 frees map[uintptr]uintptr // addr: caller 57 memAudit memReport 58 memAuditEnabled bool 59 ) 60 61 func pc2origin(pc uintptr) string { 62 f := runtime.FuncForPC(pc) 63 var fn, fns string 64 var fl int 65 if f != nil { 66 fn, fl = f.FileLine(pc) 67 fns = f.Name() 68 if x := strings.LastIndex(fns, "."); x > 0 { 69 fns = fns[x+1:] 70 } 71 } 72 return fmt.Sprintf("%s:%d:%s", fn, fl, fns) 73 } 74 75 // void *malloc(size_t size); 76 func Xmalloc(t *TLS, size types.Size_t) uintptr { 77 if size == 0 { 78 return 0 79 } 80 81 allocMu.Lock() 82 83 defer allocMu.Unlock() 84 85 p, err := allocator.UintptrCalloc(int(size)) 86 if dmesgs { 87 dmesg("%v: %v -> %#x, %v", origin(1), size, p, err) 88 } 89 if err != nil { 90 t.setErrno(errno.ENOMEM) 91 return 0 92 } 93 94 if memAuditEnabled { 95 pc, _, _, ok := runtime.Caller(1) 96 if !ok { 97 panic("cannot obtain caller's PC") 98 } 99 100 delete(frees, p) 101 if pc0, ok := allocs[p]; ok { 102 dmesg("%v: malloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)) 103 panic(fmt.Errorf("%v: malloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))) 104 } 105 106 allocs[p] = pc 107 } 108 return p 109 } 110 111 // void *calloc(size_t nmemb, size_t size); 112 func Xcalloc(t *TLS, n, size types.Size_t) uintptr { 113 rq := int(n * size) 114 if rq == 0 { 115 return 0 116 } 117 118 allocMu.Lock() 119 120 defer allocMu.Unlock() 121 122 p, err := allocator.UintptrCalloc(int(n * size)) 123 if dmesgs { 124 dmesg("%v: %v -> %#x, %v", origin(1), n*size, p, err) 125 } 126 if err != nil { 127 t.setErrno(errno.ENOMEM) 128 return 0 129 } 130 131 if memAuditEnabled { 132 pc, _, _, ok := runtime.Caller(1) 133 if !ok { 134 panic("cannot obtain caller's PC") 135 } 136 137 delete(frees, p) 138 if pc0, ok := allocs[p]; ok { 139 dmesg("%v: calloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)) 140 panic(fmt.Errorf("%v: calloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))) 141 } 142 143 allocs[p] = pc 144 } 145 return p 146 } 147 148 // void *realloc(void *ptr, size_t size); 149 func Xrealloc(t *TLS, ptr uintptr, size types.Size_t) uintptr { 150 allocMu.Lock() 151 152 defer allocMu.Unlock() 153 154 var pc uintptr 155 if memAuditEnabled { 156 var ok bool 157 if pc, _, _, ok = runtime.Caller(1); !ok { 158 panic("cannot obtain caller's PC") 159 } 160 161 if ptr != 0 { 162 if pc0, ok := frees[ptr]; ok { 163 dmesg("%v: realloc: double free of %#x, previous call at %v:", pc2origin(pc), ptr, pc2origin(pc0)) 164 panic(fmt.Errorf("%v: realloc: double free of %#x, previous call at %v:", pc2origin(pc), ptr, pc2origin(pc0))) 165 } 166 167 if _, ok := allocs[ptr]; !ok { 168 dmesg("%v: %v: realloc, free of unallocated memory: %#x", origin(1), pc2origin(pc), ptr) 169 panic(fmt.Errorf("%v: realloc, free of unallocated memory: %#x", pc2origin(pc), ptr)) 170 } 171 172 delete(allocs, ptr) 173 delete(allocsMore, ptr) 174 frees[ptr] = pc 175 } 176 } 177 178 p, err := allocator.UintptrRealloc(ptr, int(size)) 179 if dmesgs { 180 dmesg("%v: %#x, %v -> %#x, %v", origin(1), ptr, size, p, err) 181 } 182 if err != nil { 183 t.setErrno(errno.ENOMEM) 184 return 0 185 } 186 187 if memAuditEnabled && p != 0 { 188 delete(frees, p) 189 if pc0, ok := allocs[p]; ok { 190 dmesg("%v: realloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)) 191 panic(fmt.Errorf("%v: realloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))) 192 } 193 194 allocs[p] = pc 195 } 196 return p 197 } 198 199 // void free(void *ptr); 200 func Xfree(t *TLS, p uintptr) { 201 if p == 0 { 202 return 203 } 204 205 if dmesgs { 206 dmesg("%v: %#x", origin(1), p) 207 } 208 209 allocMu.Lock() 210 211 defer allocMu.Unlock() 212 213 sz := memory.UintptrUsableSize(p) 214 if memAuditEnabled { 215 pc, _, _, ok := runtime.Caller(1) 216 if !ok { 217 panic("cannot obtain caller's PC") 218 } 219 220 if pc0, ok := frees[p]; ok { 221 dmesg("%v: double free of %#x, previous call at %v:", pc2origin(pc), p, pc2origin(pc0)) 222 panic(fmt.Errorf("%v: double free of %#x, previous call at %v:", pc2origin(pc), p, pc2origin(pc0))) 223 } 224 225 if _, ok := allocs[p]; !ok { 226 dmesg("%v: free of unallocated memory: %#x", pc2origin(pc), p) 227 panic(fmt.Errorf("%v: free of unallocated memory: %#x", pc2origin(pc), p)) 228 } 229 230 delete(allocs, p) 231 delete(allocsMore, p) 232 frees[p] = pc 233 } 234 235 for i := uintptr(0); i < uintptr(sz); i++ { 236 *(*byte)(unsafe.Pointer(p + i)) = 0 237 } 238 allocator.UintptrFree(p) 239 } 240 241 func UsableSize(p uintptr) types.Size_t { 242 allocMu.Lock() 243 244 defer allocMu.Unlock() 245 246 if memAuditEnabled { 247 pc, _, _, ok := runtime.Caller(1) 248 if !ok { 249 panic("cannot obtain caller's PC") 250 } 251 252 if _, ok := allocs[p]; !ok { 253 dmesg("%v: usable size of unallocated memory: %#x", pc2origin(pc), p) 254 panic(fmt.Errorf("%v: usable size of unallocated memory: %#x", pc2origin(pc), p)) 255 } 256 } 257 258 return types.Size_t(memory.UintptrUsableSize(p)) 259 } 260 261 // MemAuditStart locks the memory allocator, initializes and enables memory 262 // auditing. Finally it unlocks the memory allocator. 263 // 264 // Some memory handling errors, like double free or freeing of unallocated 265 // memory, will panic when memory auditing is enabled. 266 // 267 // This memory auditing functionality has to be enabled using the libc.memgrind 268 // build tag. 269 // 270 // It is intended only for debug/test builds. It slows down memory allocation 271 // routines and it has additional memory costs. 272 func MemAuditStart() { 273 allocMu.Lock() 274 275 defer allocMu.Unlock() 276 277 allocs = map[uintptr]uintptr{} // addr: caller 278 allocsMore = map[uintptr]string{} 279 frees = map[uintptr]uintptr{} // addr: caller 280 memAuditEnabled = true 281 } 282 283 // MemAuditReport locks the memory allocator, reports memory leaks, if any. 284 // Finally it disables memory auditing and unlocks the memory allocator. 285 // 286 // This memory auditing functionality has to be enabled using the libc.memgrind 287 // build tag. 288 // 289 // It is intended only for debug/test builds. It slows down memory allocation 290 // routines and it has additional memory costs. 291 func MemAuditReport() (r error) { 292 allocMu.Lock() 293 294 defer func() { 295 allocs = nil 296 allocsMore = nil 297 frees = nil 298 memAuditEnabled = false 299 memAudit = nil 300 allocMu.Unlock() 301 }() 302 303 if len(allocs) != 0 { 304 for p, pc := range allocs { 305 memAudit = append(memAudit, memReportItem{p, pc, allocsMore[p]}) 306 } 307 sort.Slice(memAudit, func(i, j int) bool { 308 return memAudit[i].String() < memAudit[j].String() 309 }) 310 return memAudit 311 } 312 313 return nil 314 } 315 316 func MemAuditAnnotate(pc uintptr, s string) { 317 allocMu.Lock() 318 allocsMore[pc] = s 319 allocMu.Unlock() 320 }