| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- // Copyright 2021 The Libc Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- //go:build !libc.membrk && libc.memgrind && !(linux && (amd64 || arm64 || loong64 || ppc64le || s390x || riscv64 || 386 || arm))
- // This is a debug-only version of the memory handling functions. When a
- // program is built with -tags=libc.memgrind the functions MemAuditStart and
- // MemAuditReport can be used to check for memory leaks.
- package libc // import "modernc.org/libc"
- import (
- "fmt"
- "runtime"
- "sort"
- "strings"
- "unsafe"
- "modernc.org/libc/errno"
- "modernc.org/libc/sys/types"
- "modernc.org/memory"
- )
- const memgrind = true
- type memReportItem struct {
- p, pc uintptr
- s string
- }
- func (it *memReportItem) String() string {
- more := it.s
- if more != "" {
- a := strings.Split(more, "\n")
- more = "\n\t\t" + strings.Join(a, "\n\t\t")
- }
- return fmt.Sprintf("\t%s: %#x%s", pc2origin(it.pc), it.p, more)
- }
- type memReport []memReportItem
- func (r memReport) Error() string {
- a := []string{"memory leaks"}
- for _, v := range r {
- a = append(a, v.String())
- }
- return strings.Join(a, "\n")
- }
- var (
- allocator memory.Allocator
- allocs map[uintptr]uintptr // addr: caller
- allocsMore map[uintptr]string
- frees map[uintptr]uintptr // addr: caller
- memAudit memReport
- memAuditEnabled bool
- )
- func pc2origin(pc uintptr) string {
- f := runtime.FuncForPC(pc)
- var fn, fns string
- var fl int
- if f != nil {
- fn, fl = f.FileLine(pc)
- fns = f.Name()
- if x := strings.LastIndex(fns, "."); x > 0 {
- fns = fns[x+1:]
- }
- }
- return fmt.Sprintf("%s:%d:%s", fn, fl, fns)
- }
- // void *malloc(size_t size);
- func Xmalloc(t *TLS, size types.Size_t) uintptr {
- if __ccgo_strace {
- trc("t=%v size=%v, (%v:)", t, size, origin(2))
- }
- if size == 0 {
- // malloc(0) should return unique pointers
- // (often expected and gnulib replaces malloc if malloc(0) returns 0)
- size = 1
- }
- allocMu.Lock()
- defer allocMu.Unlock()
- p, err := allocator.UintptrCalloc(int(size))
- // if dmesgs {
- // dmesg("%v: %v -> %#x, %v", origin(1), size, p, err)
- // }
- if err != nil {
- t.setErrno(errno.ENOMEM)
- return 0
- }
- if memAuditEnabled {
- pc, _, _, ok := runtime.Caller(1)
- if !ok {
- panic("cannot obtain caller's PC")
- }
- delete(frees, p)
- if pc0, ok := allocs[p]; ok {
- dmesg("%v: malloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))
- panic(fmt.Errorf("%v: malloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)))
- }
- allocs[p] = pc
- }
- return p
- }
- // void *calloc(size_t nmemb, size_t size);
- func Xcalloc(t *TLS, n, size types.Size_t) uintptr {
- if __ccgo_strace {
- trc("t=%v n=%v size=%v, (%v:)", t, n, size, origin(2))
- }
- rq := int(n * size)
- if rq == 0 {
- rq = 1
- }
- allocMu.Lock()
- defer allocMu.Unlock()
- p, err := allocator.UintptrCalloc(rq)
- // if dmesgs {
- // dmesg("%v: %v -> %#x, %v", origin(1), n*size, p, err)
- // }
- if err != nil {
- t.setErrno(errno.ENOMEM)
- return 0
- }
- if memAuditEnabled {
- pc, _, _, ok := runtime.Caller(1)
- if !ok {
- panic("cannot obtain caller's PC")
- }
- delete(frees, p)
- if pc0, ok := allocs[p]; ok {
- dmesg("%v: calloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))
- panic(fmt.Errorf("%v: calloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)))
- }
- allocs[p] = pc
- }
- return p
- }
- // void *realloc(void *ptr, size_t size);
- func Xrealloc(t *TLS, ptr uintptr, size types.Size_t) uintptr {
- if __ccgo_strace {
- trc("t=%v ptr=%v size=%v, (%v:)", t, ptr, size, origin(2))
- }
- allocMu.Lock()
- defer allocMu.Unlock()
- var pc uintptr
- if memAuditEnabled {
- var ok bool
- if pc, _, _, ok = runtime.Caller(1); !ok {
- panic("cannot obtain caller's PC")
- }
- if ptr != 0 {
- if pc0, ok := frees[ptr]; ok {
- dmesg("%v: realloc: double free of %#x, previous call at %v:", pc2origin(pc), ptr, pc2origin(pc0))
- panic(fmt.Errorf("%v: realloc: double free of %#x, previous call at %v:", pc2origin(pc), ptr, pc2origin(pc0)))
- }
- if _, ok := allocs[ptr]; !ok {
- dmesg("%v: %v: realloc, free of unallocated memory: %#x", origin(1), pc2origin(pc), ptr)
- panic(fmt.Errorf("%v: realloc, free of unallocated memory: %#x", pc2origin(pc), ptr))
- }
- delete(allocs, ptr)
- delete(allocsMore, ptr)
- frees[ptr] = pc
- }
- }
- p, err := allocator.UintptrRealloc(ptr, int(size))
- // if dmesgs {
- // dmesg("%v: %#x, %v -> %#x, %v", origin(1), ptr, size, p, err)
- // }
- if err != nil {
- t.setErrno(errno.ENOMEM)
- return 0
- }
- if memAuditEnabled && p != 0 {
- delete(frees, p)
- if pc0, ok := allocs[p]; ok {
- dmesg("%v: realloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))
- panic(fmt.Errorf("%v: realloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)))
- }
- allocs[p] = pc
- }
- return p
- }
- // void free(void *ptr);
- func Xfree(t *TLS, p uintptr) {
- if __ccgo_strace {
- trc("t=%v p=%v, (%v:)", t, p, origin(2))
- }
- if p == 0 {
- return
- }
- // if dmesgs {
- // dmesg("%v: %#x", origin(1), p)
- // }
- allocMu.Lock()
- defer allocMu.Unlock()
- sz := memory.UintptrUsableSize(p)
- if memAuditEnabled {
- pc, _, _, ok := runtime.Caller(1)
- if !ok {
- panic("cannot obtain caller's PC")
- }
- if pc0, ok := frees[p]; ok {
- dmesg("%v: double free of %#x, previous call at %v:", pc2origin(pc), p, pc2origin(pc0))
- panic(fmt.Errorf("%v: double free of %#x, previous call at %v:", pc2origin(pc), p, pc2origin(pc0)))
- }
- if _, ok := allocs[p]; !ok {
- dmesg("%v: free of unallocated memory: %#x", pc2origin(pc), p)
- panic(fmt.Errorf("%v: free of unallocated memory: %#x", pc2origin(pc), p))
- }
- delete(allocs, p)
- delete(allocsMore, p)
- frees[p] = pc
- }
- for i := uintptr(0); i < uintptr(sz); i++ {
- *(*byte)(unsafe.Pointer(p + i)) = 0
- }
- allocator.UintptrFree(p)
- }
- func UsableSize(p uintptr) types.Size_t {
- allocMu.Lock()
- defer allocMu.Unlock()
- if memAuditEnabled {
- pc, _, _, ok := runtime.Caller(1)
- if !ok {
- panic("cannot obtain caller's PC")
- }
- if _, ok := allocs[p]; !ok {
- dmesg("%v: usable size of unallocated memory: %#x", pc2origin(pc), p)
- panic(fmt.Errorf("%v: usable size of unallocated memory: %#x", pc2origin(pc), p))
- }
- }
- return types.Size_t(memory.UintptrUsableSize(p))
- }
- func Xmalloc_usable_size(tls *TLS, p uintptr) (r Tsize_t) {
- return UsableSize(p)
- }
- type MemAllocatorStat struct {
- Allocs int
- Bytes int
- Mmaps int
- }
- // MemStat no-op for this build tag
- func MemStat() MemAllocatorStat {
- return MemAllocatorStat{}
- }
- // MemAuditStart locks the memory allocator, initializes and enables memory
- // auditing. Finally it unlocks the memory allocator.
- //
- // Some memory handling errors, like double free or freeing of unallocated
- // memory, will panic when memory auditing is enabled.
- //
- // This memory auditing functionality has to be enabled using the libc.memgrind
- // build tag.
- //
- // It is intended only for debug/test builds. It slows down memory allocation
- // routines and it has additional memory costs.
- func MemAuditStart() {
- allocMu.Lock()
- defer allocMu.Unlock()
- allocs = map[uintptr]uintptr{} // addr: caller
- allocsMore = map[uintptr]string{}
- frees = map[uintptr]uintptr{} // addr: caller
- memAuditEnabled = true
- }
- // MemAuditReport locks the memory allocator, reports memory leaks, if any.
- // Finally it disables memory auditing and unlocks the memory allocator.
- //
- // This memory auditing functionality has to be enabled using the libc.memgrind
- // build tag.
- //
- // It is intended only for debug/test builds. It slows down memory allocation
- // routines and it has additional memory costs.
- func MemAuditReport() (r error) {
- allocMu.Lock()
- defer func() {
- allocs = nil
- allocsMore = nil
- frees = nil
- memAuditEnabled = false
- memAudit = nil
- allocMu.Unlock()
- }()
- if len(allocs) != 0 {
- for p, pc := range allocs {
- memAudit = append(memAudit, memReportItem{p, pc, allocsMore[p]})
- }
- sort.Slice(memAudit, func(i, j int) bool {
- return memAudit[i].String() < memAudit[j].String()
- })
- return memAudit
- }
- return nil
- }
- func MemAuditAnnotate(pc uintptr, s string) {
- allocMu.Lock()
- allocsMore[pc] = s
- allocMu.Unlock()
- }
|